New alert system and panel de control page
This commit is contained in:
@@ -123,6 +123,15 @@ export function NewDashboardPage() {
|
||||
const { notifications: deliveryNotifications } = useDeliveryNotifications();
|
||||
const { recentNotifications: orchestrationNotifications } = useOrchestrationNotifications();
|
||||
|
||||
console.log('🔄 [Dashboard] Component render - notification counts:', {
|
||||
batch: batchNotifications.length,
|
||||
delivery: deliveryNotifications.length,
|
||||
orchestration: orchestrationNotifications.length,
|
||||
batchIds: batchNotifications.map(n => n.id).join(','),
|
||||
deliveryIds: deliveryNotifications.map(n => n.id).join(','),
|
||||
orchestrationIds: orchestrationNotifications.map(n => n.id).join(','),
|
||||
});
|
||||
|
||||
// SSE connection status
|
||||
const sseConnected = true; // Simplified - based on other notification hooks
|
||||
|
||||
@@ -152,56 +161,118 @@ export function NewDashboardPage() {
|
||||
});
|
||||
|
||||
// Track the latest notification ID to prevent re-running on same notification
|
||||
const latestBatchNotificationId = useMemo(() =>
|
||||
batchNotifications.length > 0 ? batchNotifications[0]?.id : null,
|
||||
[batchNotifications]
|
||||
);
|
||||
// Use stringified ID array to create stable dependency that only changes when IDs actually change
|
||||
const batchIdsString = JSON.stringify(batchNotifications.map(n => n.id));
|
||||
const deliveryIdsString = JSON.stringify(deliveryNotifications.map(n => n.id));
|
||||
const orchestrationIdsString = JSON.stringify(orchestrationNotifications.map(n => n.id));
|
||||
|
||||
const latestDeliveryNotificationId = useMemo(() =>
|
||||
deliveryNotifications.length > 0 ? deliveryNotifications[0]?.id : null,
|
||||
[deliveryNotifications]
|
||||
);
|
||||
console.log('📝 [Dashboard] Stringified ID arrays:', {
|
||||
batchIdsString,
|
||||
deliveryIdsString,
|
||||
orchestrationIdsString,
|
||||
});
|
||||
|
||||
const latestOrchestrationNotificationId = useMemo(() =>
|
||||
orchestrationNotifications.length > 0 ? orchestrationNotifications[0]?.id : null,
|
||||
[orchestrationNotifications]
|
||||
);
|
||||
const latestBatchNotificationId = useMemo(() => {
|
||||
const result = batchNotifications.length === 0 ? '' : (batchNotifications[0]?.id || '');
|
||||
console.log('🧮 [Dashboard] latestBatchNotificationId useMemo recalculated:', {
|
||||
result,
|
||||
dependency: batchIdsString,
|
||||
notificationCount: batchNotifications.length,
|
||||
});
|
||||
return result;
|
||||
}, [batchIdsString]);
|
||||
|
||||
const latestDeliveryNotificationId = useMemo(() => {
|
||||
const result = deliveryNotifications.length === 0 ? '' : (deliveryNotifications[0]?.id || '');
|
||||
console.log('🧮 [Dashboard] latestDeliveryNotificationId useMemo recalculated:', {
|
||||
result,
|
||||
dependency: deliveryIdsString,
|
||||
notificationCount: deliveryNotifications.length,
|
||||
});
|
||||
return result;
|
||||
}, [deliveryIdsString]);
|
||||
|
||||
const latestOrchestrationNotificationId = useMemo(() => {
|
||||
const result = orchestrationNotifications.length === 0 ? '' : (orchestrationNotifications[0]?.id || '');
|
||||
console.log('🧮 [Dashboard] latestOrchestrationNotificationId useMemo recalculated:', {
|
||||
result,
|
||||
dependency: orchestrationIdsString,
|
||||
notificationCount: orchestrationNotifications.length,
|
||||
});
|
||||
return result;
|
||||
}, [orchestrationIdsString]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentBatchNotificationId = latestBatchNotificationId || '';
|
||||
if (currentBatchNotificationId &&
|
||||
currentBatchNotificationId !== prevBatchNotificationsRef.current) {
|
||||
prevBatchNotificationsRef.current = currentBatchNotificationId;
|
||||
console.log('⚡ [Dashboard] batchNotifications useEffect triggered', {
|
||||
latestBatchNotificationId,
|
||||
prevValue: prevBatchNotificationsRef.current,
|
||||
hasChanged: latestBatchNotificationId !== prevBatchNotificationsRef.current,
|
||||
notificationCount: batchNotifications.length,
|
||||
firstNotification: batchNotifications[0],
|
||||
});
|
||||
|
||||
if (latestBatchNotificationId &&
|
||||
latestBatchNotificationId !== prevBatchNotificationsRef.current) {
|
||||
console.log('🔥 [Dashboard] NEW batch notification detected, updating ref and refetching');
|
||||
prevBatchNotificationsRef.current = latestBatchNotificationId;
|
||||
const latest = batchNotifications[0];
|
||||
|
||||
if (['batch_completed', 'batch_started'].includes(latest.event_type)) {
|
||||
console.log('🚀 [Dashboard] Triggering refetch for batch event:', latest.event_type);
|
||||
refetchCallbacksRef.current.refetchExecutionProgress();
|
||||
refetchCallbacksRef.current.refetchHealth();
|
||||
} else {
|
||||
console.log('⏭️ [Dashboard] Skipping refetch - event type not relevant:', latest.event_type);
|
||||
}
|
||||
}
|
||||
}, [latestBatchNotificationId]); // Only run when a NEW notification arrives
|
||||
|
||||
useEffect(() => {
|
||||
const currentDeliveryNotificationId = latestDeliveryNotificationId || '';
|
||||
if (currentDeliveryNotificationId &&
|
||||
currentDeliveryNotificationId !== prevDeliveryNotificationsRef.current) {
|
||||
prevDeliveryNotificationsRef.current = currentDeliveryNotificationId;
|
||||
console.log('⚡ [Dashboard] deliveryNotifications useEffect triggered', {
|
||||
latestDeliveryNotificationId,
|
||||
prevValue: prevDeliveryNotificationsRef.current,
|
||||
hasChanged: latestDeliveryNotificationId !== prevDeliveryNotificationsRef.current,
|
||||
notificationCount: deliveryNotifications.length,
|
||||
firstNotification: deliveryNotifications[0],
|
||||
});
|
||||
|
||||
if (latestDeliveryNotificationId &&
|
||||
latestDeliveryNotificationId !== prevDeliveryNotificationsRef.current) {
|
||||
console.log('🔥 [Dashboard] NEW delivery notification detected, updating ref and refetching');
|
||||
prevDeliveryNotificationsRef.current = latestDeliveryNotificationId;
|
||||
const latest = deliveryNotifications[0];
|
||||
|
||||
if (['delivery_received', 'delivery_overdue'].includes(latest.event_type)) {
|
||||
console.log('🚀 [Dashboard] Triggering refetch for delivery event:', latest.event_type);
|
||||
refetchCallbacksRef.current.refetchExecutionProgress();
|
||||
refetchCallbacksRef.current.refetchHealth();
|
||||
} else {
|
||||
console.log('⏭️ [Dashboard] Skipping refetch - event type not relevant:', latest.event_type);
|
||||
}
|
||||
}
|
||||
}, [latestDeliveryNotificationId]); // Only run when a NEW notification arrives
|
||||
|
||||
useEffect(() => {
|
||||
const currentOrchestrationNotificationId = latestOrchestrationNotificationId || '';
|
||||
if (currentOrchestrationNotificationId &&
|
||||
currentOrchestrationNotificationId !== prevOrchestrationNotificationsRef.current) {
|
||||
prevOrchestrationNotificationsRef.current = currentOrchestrationNotificationId;
|
||||
console.log('⚡ [Dashboard] orchestrationNotifications useEffect triggered', {
|
||||
latestOrchestrationNotificationId,
|
||||
prevValue: prevOrchestrationNotificationsRef.current,
|
||||
hasChanged: latestOrchestrationNotificationId !== prevOrchestrationNotificationsRef.current,
|
||||
notificationCount: orchestrationNotifications.length,
|
||||
firstNotification: orchestrationNotifications[0],
|
||||
});
|
||||
|
||||
if (latestOrchestrationNotificationId &&
|
||||
latestOrchestrationNotificationId !== prevOrchestrationNotificationsRef.current) {
|
||||
console.log('🔥 [Dashboard] NEW orchestration notification detected, updating ref and refetching');
|
||||
prevOrchestrationNotificationsRef.current = latestOrchestrationNotificationId;
|
||||
const latest = orchestrationNotifications[0];
|
||||
|
||||
if (latest.event_type === 'orchestration_run_completed') {
|
||||
console.log('🚀 [Dashboard] Triggering refetch for orchestration event:', latest.event_type);
|
||||
refetchCallbacksRef.current.refetchOrchestration();
|
||||
refetchCallbacksRef.current.refetchActionQueue();
|
||||
} else {
|
||||
console.log('⏭️ [Dashboard] Skipping refetch - event type not relevant:', latest.event_type);
|
||||
}
|
||||
}
|
||||
}, [latestOrchestrationNotificationId]); // Only run when a NEW notification arrives
|
||||
@@ -432,27 +503,21 @@ export function NewDashboardPage() {
|
||||
// Note: startTour removed from deps to prevent infinite loop - the effect guards with sessionStorage ensure it only runs once
|
||||
|
||||
return (
|
||||
<div className="min-h-screen pb-20 md:pb-8">
|
||||
<div className="min-h-screen pb-20 md:pb-8 bg-[var(--bg-primary)]">
|
||||
{/* Mobile-optimized container */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl md:text-4xl font-bold" style={{ color: 'var(--text-primary)' }}>{t('dashboard:title')}</h1>
|
||||
<p className="mt-1" style={{ color: 'var(--text-secondary)' }}>{t('dashboard:subtitle')}</p>
|
||||
<h1 className="text-3xl md:text-4xl font-bold text-[var(--text-primary)]">{t('dashboard:title')}</h1>
|
||||
<p className="mt-1 text-[var(--text-secondary)]">{t('dashboard:subtitle')}</p>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={handleRefreshAll}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg font-semibold transition-colors duration-200"
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-primary)',
|
||||
borderColor: 'var(--border-primary)',
|
||||
border: '1px solid',
|
||||
color: 'var(--text-secondary)'
|
||||
}}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg font-semibold transition-colors duration-200 border border-[var(--border-primary)] bg-[var(--bg-primary)] text-[var(--text-secondary)]"
|
||||
>
|
||||
<RefreshCw className="w-5 h-5" />
|
||||
<span className="hidden sm:inline">{t('common:actions.refresh')}</span>
|
||||
@@ -461,23 +526,19 @@ export function NewDashboardPage() {
|
||||
{/* Unified Add Button with Keyboard Shortcut */}
|
||||
<button
|
||||
onClick={() => setIsAddWizardOpen(true)}
|
||||
className="group relative flex items-center gap-2 px-6 py-2.5 rounded-lg font-semibold transition-all duration-200 shadow-lg hover:shadow-xl hover:-translate-y-0.5 active:translate-y-0"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%)',
|
||||
color: 'white'
|
||||
}}
|
||||
className="group relative flex items-center gap-2 px-6 py-2.5 rounded-lg font-semibold transition-all duration-200 shadow-lg hover:shadow-xl hover:-translate-y-0.5 active:translate-y-0 bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] text-white"
|
||||
title={`Quick Add (${navigator.platform.includes('Mac') ? 'Cmd' : 'Ctrl'}+K)`}
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
<span className="hidden sm:inline">{t('common:actions.add')}</span>
|
||||
<Sparkles className="w-4 h-4 opacity-80" />
|
||||
{/* Keyboard shortcut badge - shown on hover */}
|
||||
<span className="hidden lg:flex absolute -bottom-8 left-1/2 -translate-x-1/2 items-center gap-1 px-2 py-1 rounded text-xs font-mono opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap pointer-events-none" style={{ backgroundColor: 'var(--bg-primary)', color: 'var(--text-secondary)', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
|
||||
<kbd className="px-1.5 py-0.5 rounded text-xs font-semibold" style={{ backgroundColor: 'var(--bg-tertiary)', border: '1px solid var(--border-secondary)' }}>
|
||||
<span className="hidden lg:flex absolute -bottom-8 left-1/2 -translate-x-1/2 items-center gap-1 px-2 py-1 rounded text-xs font-mono opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap pointer-events-none bg-[var(--bg-primary)] text-[var(--text-secondary)] shadow-sm">
|
||||
<kbd className="px-1.5 py-0.5 rounded text-xs font-semibold bg-[var(--bg-tertiary)] border border-[var(--border-secondary)]">
|
||||
{navigator.platform.includes('Mac') ? '⌘' : 'Ctrl'}
|
||||
</kbd>
|
||||
<span>+</span>
|
||||
<kbd className="px-1.5 py-0.5 rounded text-xs font-semibold" style={{ backgroundColor: 'var(--bg-tertiary)', border: '1px solid var(--border-secondary)' }}>
|
||||
<kbd className="px-1.5 py-0.5 rounded text-xs font-semibold bg-[var(--bg-tertiary)] border border-[var(--border-secondary)]">
|
||||
K
|
||||
</kbd>
|
||||
</span>
|
||||
@@ -545,43 +606,39 @@ export function NewDashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* SECTION 6: Quick Action Links */}
|
||||
<div className="rounded-xl shadow-lg p-6 border" style={{ backgroundColor: 'var(--bg-primary)', borderColor: 'var(--border-primary)' }}>
|
||||
<h2 className="text-xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>{t('dashboard:sections.quick_actions')}</h2>
|
||||
<div className="rounded-xl shadow-lg p-6 border border-[var(--border-primary)] bg-[var(--bg-primary)]">
|
||||
<h2 className="text-xl font-bold mb-4 text-[var(--text-primary)]">{t('dashboard:sections.quick_actions')}</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<button
|
||||
onClick={() => navigate('/app/operations/procurement')}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group"
|
||||
style={{ backgroundColor: 'var(--bg-tertiary)', borderLeft: '4px solid var(--color-info)' }}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group bg-[var(--bg-tertiary)] border-l-4 border-l-[var(--color-info)]"
|
||||
>
|
||||
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>{t('dashboard:quick_actions.view_orders')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" style={{ color: 'var(--color-info)' }} />
|
||||
<span className="font-semibold text-[var(--text-primary)]">{t('dashboard:quick_actions.view_orders')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200 text-[var(--color-info)]" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/app/operations/production')}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group"
|
||||
style={{ backgroundColor: 'var(--bg-tertiary)', borderLeft: '4px solid var(--color-success)' }}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group bg-[var(--bg-tertiary)] border-l-4 border-l-[var(--color-success)]"
|
||||
>
|
||||
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>{t('dashboard:quick_actions.view_production')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" style={{ color: 'var(--color-success)' }} />
|
||||
<span className="font-semibold text-[var(--text-primary)]">{t('dashboard:quick_actions.view_production')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200 text-[var(--color-success)]" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/app/database/inventory')}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group"
|
||||
style={{ backgroundColor: 'var(--bg-tertiary)', borderLeft: '4px solid var(--color-secondary)' }}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group bg-[var(--bg-tertiary)] border-l-4 border-l-[var(--color-secondary)]"
|
||||
>
|
||||
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>{t('dashboard:quick_actions.view_inventory')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" style={{ color: 'var(--color-secondary)' }} />
|
||||
<span className="font-semibold text-[var(--text-primary)]">{t('dashboard:quick_actions.view_inventory')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200 text-[var(--color-secondary)]" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/app/database/suppliers')}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group"
|
||||
style={{ backgroundColor: 'var(--bg-tertiary)', borderLeft: '4px solid var(--color-warning)' }}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group bg-[var(--bg-tertiary)] border-l-4 border-l-[var(--color-warning)]"
|
||||
>
|
||||
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>{t('dashboard:quick_actions.view_suppliers')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" style={{ color: 'var(--color-warning)' }} />
|
||||
<span className="font-semibold text-[var(--text-primary)]">{t('dashboard:quick_actions.view_suppliers')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200 text-[var(--color-warning)]" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user