import { useState, useMemo, useCallback, useRef } from 'react'; import { useNavigate } from 'react-router'; import { useGlobalFilters } from '@cameleer/design-system'; import { useExecutionDetail } from '../../api/queries/executions'; import { useDiagramByRoute } from '../../api/queries/diagrams'; import { useRouteCatalog } from '../../api/queries/catalog'; import type { NodeAction } from '../../components/ProcessDiagram/types'; import { ExchangeHeader } from './ExchangeHeader'; import { ExecutionDiagram } from '../../components/ExecutionDiagram/ExecutionDiagram'; import { ProcessDiagram } from '../../components/ProcessDiagram'; import styles from './ExchangesPage.module.css'; import Dashboard from '../Dashboard/Dashboard'; import type { SelectedExchange } from '../Dashboard/Dashboard'; export default function ExchangesPage() { const [selected, setSelected] = useState(null); const [splitPercent, setSplitPercent] = useState(50); const containerRef = useRef(null); const handleExchangeSelect = useCallback((exchange: SelectedExchange) => { setSelected(exchange); }, []); const handleCorrelatedSelect = useCallback((executionId: string, applicationName: string, routeId: string) => { setSelected({ executionId, applicationName, routeId }); }, []); const handleClearSelection = useCallback(() => { setSelected(null); }, []); const handleSplitterDown = useCallback((e: React.PointerEvent) => { e.currentTarget.setPointerCapture(e.pointerId); const container = containerRef.current; if (!container) return; const onMove = (me: PointerEvent) => { const rect = container.getBoundingClientRect(); const x = me.clientX - rect.left; const pct = Math.min(80, Math.max(20, (x / rect.width) * 100)); setSplitPercent(pct); }; const onUp = () => { document.removeEventListener('pointermove', onMove); document.removeEventListener('pointerup', onUp); }; document.addEventListener('pointermove', onMove); document.addEventListener('pointerup', onUp); }, []); // No exchange selected: full-width Dashboard if (!selected) { return ; } // Exchange selected: resizable split — Dashboard on left, diagram on right return (
); } // ─── Right panel: diagram + execution overlay ─────────────────────────────── interface DiagramPanelProps { appId: string; routeId: string; exchangeId: string; onCorrelatedSelect: (executionId: string, applicationName: string, routeId: string) => void; onClearSelection: () => void; } function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearSelection }: DiagramPanelProps) { const navigate = useNavigate(); const { timeRange } = useGlobalFilters(); const timeFrom = timeRange.start.toISOString(); const timeTo = timeRange.end.toISOString(); const { data: detail } = useExecutionDetail(exchangeId); const { data: catalog } = useRouteCatalog(timeFrom, timeTo); const knownRouteIds = useMemo(() => { const ids = new Set(); if (catalog) { for (const app of catalog as any[]) { for (const r of app.routes || []) { ids.add(r.routeId); } } } return ids; }, [catalog]); const handleNodeAction = useCallback((nodeId: string, action: NodeAction) => { if (action === 'configure-tap') { navigate(`/admin/appconfig?app=${encodeURIComponent(appId)}&processor=${encodeURIComponent(nodeId)}`); } }, [appId, navigate]); if (detail) { return ( <> ); } return (
Loading execution...
); }