Files
bakery-ia/frontend/src/components/dashboard/NetworkOverviewTab.tsx
2025-12-17 20:50:22 +01:00

340 lines
14 KiB
TypeScript

/*
* Network Overview Tab Component for Enterprise Dashboard
* Shows network-wide status and critical alerts
*/
import React, { useEffect, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/Card';
import { Button } from '../ui/Button';
import { Network, AlertTriangle, CheckCircle2, Activity, TrendingUp, Bell, Clock } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import SystemStatusBlock from './blocks/SystemStatusBlock';
import NetworkSummaryCards from './NetworkSummaryCards';
import { useControlPanelData } from '../../api/hooks/useControlPanelData';
import { useNetworkSummary } from '../../api/hooks/useEnterpriseDashboard';
import { useSSEEvents } from '../../hooks/useSSE';
interface NetworkOverviewTabProps {
tenantId: string;
onOutletClick?: (outletId: string, outletName: string) => void;
}
const NetworkOverviewTab: React.FC<NetworkOverviewTabProps> = ({ tenantId, onOutletClick }) => {
const { t } = useTranslation('dashboard');
// Get network-wide control panel data (for system status)
const { data: controlPanelData, isLoading: isControlPanelLoading } = useControlPanelData(tenantId);
// Get network summary data
const { data: networkSummary, isLoading: isNetworkSummaryLoading } = useNetworkSummary(tenantId);
// Real-time SSE events
const { events: sseEvents, isConnected: sseConnected } = useSSEEvents({
channels: ['*.alerts', '*.notifications', 'recommendations']
});
// State for real-time notifications
const [recentEvents, setRecentEvents] = useState<any[]>([]);
const [showAllEvents, setShowAllEvents] = useState(false);
// Process SSE events for real-time notifications
useEffect(() => {
if (sseEvents.length === 0) return;
// Filter relevant events for network overview
const relevantEventTypes = [
'network_alert', 'outlet_performance_update', 'distribution_route_update',
'batch_completed', 'batch_started', 'delivery_received', 'delivery_overdue',
'equipment_maintenance', 'production_delay', 'stock_receipt_incomplete'
];
const networkEvents = sseEvents.filter(event =>
relevantEventTypes.includes(event.event_type)
);
// Keep only the 5 most recent events
setRecentEvents(networkEvents.slice(0, 5));
}, [sseEvents]);
const isLoading = isControlPanelLoading || isNetworkSummaryLoading;
return (
<div className="space-y-8">
{/* Network Status Block - Reusing SystemStatusBlock with network-wide data */}
<div className="mb-8">
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-4 flex items-center gap-2">
<Network className="w-6 h-6 text-[var(--color-primary)]" />
{t('enterprise.network_status')}
</h2>
<SystemStatusBlock
data={controlPanelData}
loading={isControlPanelLoading}
/>
</div>
{/* Network Summary Cards */}
<div className="mb-8">
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-4 flex items-center gap-2">
<Activity className="w-6 h-6 text-[var(--color-success)]" />
{t('enterprise.network_summary')}
</h2>
<NetworkSummaryCards
data={networkSummary}
isLoading={isNetworkSummaryLoading}
/>
</div>
{/* Quick Actions */}
<div className="mb-8">
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-4 flex items-center gap-2">
<TrendingUp className="w-6 h-6 text-[var(--color-info)]" />
{t('enterprise.quick_actions')}
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-3 mb-4">
<Network className="w-6 h-6 text-[var(--color-primary)]" />
<h3 className="text-lg font-semibold text-[var(--text-primary)]">{t('enterprise.add_outlet')}</h3>
</div>
<p className="text-[var(--text-secondary)] mb-4">{t('enterprise.add_outlet_description')}</p>
<Button
onClick={() => window.location.href = `/app/tenants/${tenantId}/settings/organization`}
className="w-full"
>
{t('enterprise.create_outlet')}
</Button>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-3 mb-4">
<CheckCircle2 className="w-6 h-6 text-[var(--color-success)]" />
<h3 className="text-lg font-semibold text-[var(--text-primary)]">{t('enterprise.internal_transfers')}</h3>
</div>
<p className="text-[var(--text-secondary)] mb-4">{t('enterprise.manage_transfers')}</p>
<Button
onClick={() => window.location.href = `/app/tenants/${tenantId}/procurement/internal-transfers`}
variant="outline"
className="w-full"
>
{t('enterprise.view_transfers')}
</Button>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex items-center gap-3 mb-4">
<AlertTriangle className="w-6 h-6 text-[var(--color-warning)]" />
<h3 className="text-lg font-semibold text-[var(--text-primary)]">{t('enterprise.view_alerts')}</h3>
</div>
<p className="text-[var(--text-secondary)] mb-4">{t('enterprise.network_alerts_description')}</p>
<Button
onClick={() => window.location.href = `/app/tenants/${tenantId}/alerts`}
variant="outline"
className="w-full"
>
{t('enterprise.view_all_alerts')}
</Button>
</CardContent>
</Card>
</div>
</div>
{/* Network Health Indicators */}
<div className="mb-8">
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-4 flex items-center gap-2">
<CheckCircle2 className="w-6 h-6 text-[var(--color-success)]" />
{t('enterprise.network_health')}
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{/* On-time Delivery Rate */}
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
{t('enterprise.on_time_delivery')}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-[var(--color-success)]">
{controlPanelData?.orchestrationSummary?.aiHandlingRate || 0}%
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('enterprise.delivery_performance')}
</p>
</CardContent>
</Card>
{/* Issue Prevention Rate */}
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
{t('enterprise.issue_prevention')}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-[var(--color-primary)]">
{controlPanelData?.issuesPreventedByAI || 0}
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('enterprise.issues_prevented')}
</p>
</CardContent>
</Card>
{/* Active Issues */}
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
{t('enterprise.active_issues')}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-[var(--color-warning)]">
{controlPanelData?.issuesRequiringAction || 0}
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('enterprise.action_required')}
</p>
</CardContent>
</Card>
{/* Network Efficiency */}
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
{t('enterprise.network_efficiency')}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-[var(--color-info)]">
{Math.round((controlPanelData?.issuesPreventedByAI || 0) /
Math.max(1, (controlPanelData?.issuesPreventedByAI || 0) + (controlPanelData?.issuesRequiringAction || 0)) * 100) || 0}%
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('enterprise.operational_efficiency')}
</p>
</CardContent>
</Card>
</div>
</div>
{/* Real-time Events Notification */}
<div className="mb-8">
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-4 flex items-center gap-2">
<Bell className="w-6 h-6 text-[var(--color-info)]" />
{t('enterprise.real_time_events')}
</h2>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<div className="flex items-center gap-2">
<CardTitle className="text-sm font-medium text-[var(--text-secondary)]">
{t('enterprise.recent_activity')}
</CardTitle>
{sseConnected ? (
<div className="flex items-center gap-1 text-xs text-[var(--color-success)]">
<span className="w-2 h-2 rounded-full bg-[var(--color-success)] animate-pulse"></span>
{t('enterprise.live_updates')}
</div>
) : (
<div className="flex items-center gap-1 text-xs text-[var(--color-warning)]">
<span className="w-2 h-2 rounded-full bg-[var(--color-warning)]"></span>
{t('enterprise.offline')}
</div>
)}
</div>
</CardHeader>
<CardContent>
{recentEvents.length > 0 ? (
<div className="space-y-4">
{recentEvents.slice(0, showAllEvents ? recentEvents.length : 3).map((event, index) => {
// Determine event icon and color based on type
const getEventConfig = () => {
switch (event.event_type) {
case 'network_alert':
case 'production_delay':
case 'equipment_maintenance':
return { icon: AlertTriangle, color: 'text-[var(--color-warning)]' };
case 'batch_completed':
case 'delivery_received':
return { icon: CheckCircle2, color: 'text-[var(--color-success)]' };
case 'batch_started':
case 'outlet_performance_update':
return { icon: Activity, color: 'text-[var(--color-info)]' };
case 'delivery_overdue':
case 'stock_receipt_incomplete':
return { icon: Clock, color: 'text-[var(--color-danger)]' };
default:
return { icon: Bell, color: 'text-[var(--color-primary)]' };
}
};
const { icon: EventIcon, color } = getEventConfig();
const eventTime = new Date(event.timestamp || event.created_at || Date.now());
return (
<div key={index} className="flex items-start gap-3 p-3 rounded-lg border border-[var(--border-primary)]">
<div className={`p-2 rounded-lg ${color.replace('text', 'bg')}`}>
<EventIcon className={`w-5 h-5 ${color}`} />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<p className="font-medium text-[var(--text-primary)]">
{event.event_type.replace(/_/g, ' ')}
</p>
<p className="text-xs text-[var(--text-tertiary)] whitespace-nowrap">
{eventTime.toLocaleTimeString()}
</p>
</div>
{event.message && (
<p className="text-sm text-[var(--text-secondary)] mt-1">
{event.message}
</p>
)}
{event.entity_type && event.entity_id && (
<p className="text-xs text-[var(--text-tertiary)] mt-1">
{event.entity_type}: {event.entity_id}
</p>
)}
</div>
</div>
);
})}
{recentEvents.length > 3 && !showAllEvents && (
<Button
onClick={() => setShowAllEvents(true)}
variant="outline"
size="sm"
className="w-full mt-2"
>
{t('enterprise.show_all_events', { count: recentEvents.length })}
</Button>
)}
{showAllEvents && recentEvents.length > 3 && (
<Button
onClick={() => setShowAllEvents(false)}
variant="outline"
size="sm"
className="w-full mt-2"
>
{t('enterprise.show_less')}
</Button>
)}
</div>
) : (
<div className="text-center py-8 text-[var(--text-secondary)]">
{sseConnected ? t('enterprise.no_recent_activity') : t('enterprise.waiting_for_updates')}
</div>
)}
</CardContent>
</Card>
</div>
</div>
);
};
export default NetworkOverviewTab;