From 29f4be542b9ccef2e25bffb2283cf28a85339c56 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sat, 28 Mar 2026 15:20:17 +0100 Subject: [PATCH] fix(ui): exchange selection uses state, not URL navigation Row click no longer navigates to /exchanges/:app/:route/:id which was changing the search scope. Instead, Dashboard calls onExchangeSelect callback and ExchangesPage manages the selected exchange as local state. The search criteria and scope are preserved when selecting an exchange. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/pages/Dashboard/Dashboard.tsx | 21 ++++++-- ui/src/pages/Exchanges/ExchangesPage.tsx | 68 +++++++++--------------- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/ui/src/pages/Dashboard/Dashboard.tsx b/ui/src/pages/Dashboard/Dashboard.tsx index 0acc1b79..6ace5913 100644 --- a/ui/src/pages/Dashboard/Dashboard.tsx +++ b/ui/src/pages/Dashboard/Dashboard.tsx @@ -176,7 +176,17 @@ function buildBaseColumns(): Column[] { // ─── Dashboard component ───────────────────────────────────────────────────── -export default function Dashboard() { +export interface SelectedExchange { + executionId: string; + applicationName: string; + routeId: string; +} + +interface DashboardProps { + onExchangeSelect?: (exchange: SelectedExchange) => void; +} + +export default function Dashboard({ onExchangeSelect }: DashboardProps = {}) { const { appId, routeId } = useParams<{ appId: string; routeId: string }>() const navigate = useNavigate() const [searchParams, setSearchParams] = useSearchParams() @@ -228,8 +238,13 @@ export default function Dashboard() { function handleRowClick(row: Row) { setSelectedId(row.id) - // Navigate to the split view with diagram - navigate(`/exchanges/${row.applicationName}/${row.routeId}/${row.executionId}`) + if (onExchangeSelect) { + onExchangeSelect({ + executionId: row.executionId, + applicationName: row.applicationName ?? '', + routeId: row.routeId, + }) + } } function handleRowAccent(row: Row): 'error' | 'warning' | undefined { diff --git a/ui/src/pages/Exchanges/ExchangesPage.tsx b/ui/src/pages/Exchanges/ExchangesPage.tsx index e7c092b0..b3baea25 100644 --- a/ui/src/pages/Exchanges/ExchangesPage.tsx +++ b/ui/src/pages/Exchanges/ExchangesPage.tsx @@ -1,5 +1,5 @@ import { useState, useMemo, useCallback, useRef } from 'react'; -import { useParams, useNavigate } from 'react-router'; +import { useNavigate } from 'react-router'; import { useGlobalFilters } from '@cameleer/design-system'; import { useExecutionDetail } from '../../api/queries/executions'; import { useDiagramByRoute } from '../../api/queries/diagrams'; @@ -10,35 +10,18 @@ import { ExecutionDiagram } from '../../components/ExecutionDiagram/ExecutionDia 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'; +import type { SelectedExchange } 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 [selected, setSelected] = useState(null); const [splitPercent, setSplitPercent] = useState(50); const containerRef = useRef(null); + const handleExchangeSelect = useCallback((exchange: SelectedExchange) => { + setSelected(exchange); + }, []); + const handleSplitterDown = useCallback((e: React.PointerEvent) => { e.currentTarget.setPointerCapture(e.pointerId); const container = containerRef.current; @@ -57,25 +40,35 @@ function SplitExchangeView({ appId, routeId, exchangeId }: SplitExchangeViewProp 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 + optional execution overlay ────────────────────── +// ─── Right panel: diagram + execution overlay ─────────────────────────────── interface DiagramPanelProps { appId: string; routeId: string; - exchangeId?: string; + exchangeId: string; } function DiagramPanel({ appId, routeId, exchangeId }: DiagramPanelProps) { @@ -84,8 +77,7 @@ function DiagramPanel({ appId, routeId, exchangeId }: DiagramPanelProps) { const timeFrom = timeRange.start.toISOString(); const timeTo = timeRange.end.toISOString(); - const { data: detail } = useExecutionDetail(exchangeId ?? null); - const diagramQuery = useDiagramByRoute(appId, routeId); + const { data: detail } = useExecutionDetail(exchangeId); const { data: catalog } = useRouteCatalog(timeFrom, timeTo); const knownRouteIds = useMemo(() => { @@ -106,7 +98,7 @@ function DiagramPanel({ appId, routeId, exchangeId }: DiagramPanelProps) { } }, [appId, navigate]); - if (exchangeId && detail) { + if (detail) { return ( <> @@ -120,21 +112,9 @@ function DiagramPanel({ appId, routeId, exchangeId }: DiagramPanelProps) { ); } - if (diagramQuery.data) { - return ( - - ); - } - return (
- Loading diagram... + Loading execution...
); }