import { useState, useMemo, useCallback, useRef } from 'react'; import { useParams, 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'; // The full-width Dashboard table — used at every scope level import Dashboard from '../Dashboard/Dashboard'; export default function ExchangesPage() { const { appId, routeId, exchangeId } = useParams<{ appId?: string; routeId?: string; exchangeId?: string; }>(); // No route scoped: full-width Dashboard if (!routeId) { return ; } // Route scoped: resizable split — Dashboard table on left, diagram on right return ; } // ─── Resizable split view ─────────────────────────────────────────────────── interface SplitExchangeViewProps { appId: string; routeId: string; exchangeId?: string; } function SplitExchangeView({ appId, routeId, exchangeId }: SplitExchangeViewProps) { const [splitPercent, setSplitPercent] = useState(50); const containerRef = useRef(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); }, []); return (
); } // ─── Right panel: diagram + optional execution overlay ────────────────────── interface DiagramPanelProps { appId: string; routeId: string; exchangeId?: string; } function DiagramPanel({ appId, routeId, exchangeId }: DiagramPanelProps) { const navigate = useNavigate(); const { timeRange } = useGlobalFilters(); const timeFrom = timeRange.start.toISOString(); const timeTo = timeRange.end.toISOString(); const { data: detail } = useExecutionDetail(exchangeId ?? null); const diagramQuery = useDiagramByRoute(appId, routeId); 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 (exchangeId && detail) { return ( <> ); } if (diagramQuery.data) { return ( ); } return (
Loading diagram...
); }