Imporve enterprise
This commit is contained in:
340
frontend/src/components/dashboard/NetworkOverviewTab.tsx
Normal file
340
frontend/src/components/dashboard/NetworkOverviewTab.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* 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;
|
||||
Reference in New Issue
Block a user