Реалізація Real-Time Dashboard (оновлення даних без перезагрузки)
Real-Time Dashboard відображає актуальні дані без ручного оновлення сторінки — метрики, статуси замовлень, аналітику, KPI. Оновлення відбувається через WebSocket або SSE в момент зміни даних.
Вибір технології
WebSocket — двунаправленний, підходить для інтерактивних дашбордів з фільтрами. Server-Sent Events (SSE) — однонаправленний сервер → клієнт, простіше, працює через HTTP/2. Polling — кожні N секунд робити запит. Просто, але менш ефективно.
Для дашборду без дій користувача SSE достатньо й дешевше у реалізації.
SSE Endpoint
// GET /api/dashboard/stream
app.get('/api/dashboard/stream', authenticate, async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no'); // для nginx
const sendEvent = (event: string, data: unknown) => {
res.write(`event: ${event}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// Відправити вихідні дані
const initial = await dashboardService.getMetrics(req.user.id);
sendEvent('init', initial);
// Підписатися на Redis pub/sub для оновлень
const subscriber = redis.duplicate();
await subscriber.subscribe(`dashboard:${req.user.id}`);
subscriber.on('message', (channel, message) => {
const update = JSON.parse(message);
sendEvent(update.type, update.data);
});
// Heartbeat кожні 30 сек щоб не закрилося з'єднання
const heartbeat = setInterval(() => {
res.write(':heartbeat\n\n');
}, 30000);
req.on('close', () => {
clearInterval(heartbeat);
subscriber.unsubscribe();
subscriber.quit();
});
});
React Hook для SSE
function useDashboardStream(userId: string) {
const [metrics, setMetrics] = useState<DashboardMetrics | null>(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const eventSource = new EventSource('/api/dashboard/stream', {
withCredentials: true
});
eventSource.addEventListener('init', (e) => {
setMetrics(JSON.parse(e.data));
setIsConnected(true);
});
eventSource.addEventListener('metrics:updated', (e) => {
const update = JSON.parse(e.data);
setMetrics(prev => prev ? { ...prev, ...update } : update);
});
eventSource.addEventListener('order:new', (e) => {
const order = JSON.parse(e.data);
setMetrics(prev => prev ? {
...prev,
todayOrders: prev.todayOrders + 1,
todayRevenue: prev.todayRevenue + order.total
} : null);
});
eventSource.onerror = () => {
setIsConnected(false);
// EventSource автоматично переподключається
};
return () => eventSource.close();
}, [userId]);
return { metrics, isConnected };
}
Публікація оновлень з backend
// При створенні замовлення — повідомити активні дашборди
async function onOrderCreated(order: Order) {
// Опублікувати менеджеру
await redis.publish(`dashboard:${order.managerId}`, JSON.stringify({
type: 'order:new',
data: { id: order.id, total: order.total, status: order.status }
}));
// Опублікувати в загальний канал для admin-дашборду
await redis.publish('dashboard:admin', JSON.stringify({
type: 'metrics:updated',
data: { todayOrders: await countTodayOrders(), todayRevenue: await sumTodayRevenue() }
}));
}
Дашборд з Socket.IO та віджетами
function DashboardPage() {
const { metrics, isConnected } = useDashboardStream(user.id);
return (
<div className="dashboard-grid">
<ConnectionIndicator isConnected={isConnected} />
<MetricCard
title="Замовлення сьогодні"
value={metrics?.todayOrders ?? 0}
delta={metrics?.ordersVsYesterday}
/>
<MetricCard
title="Виручка"
value={formatCurrency(metrics?.todayRevenue ?? 0)}
delta={metrics?.revenueVsYesterday}
/>
<LiveOrderFeed orders={metrics?.recentOrders ?? []} />
<RealtimeChart
data={metrics?.hourlyRevenue ?? []}
title="Виручка по годинам"
/>
</div>
);
}
Часові рамки
SSE endpoint + Redis pub/sub + React-хук + базові віджети: 1–2 тижні. Повний дашборд з фільтрами, експортом та кількома типами метрик: 3–4 тижні.







