feat: Cmd+K Enter applies full-text search to dashboard
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m47s
CI / docker (push) Successful in 1m16s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 36s

When pressing Enter in the command palette without explicitly selecting
a result (via arrow keys or mouse), the search query is now applied as
a server-side full-text filter on the Dashboard table. Explicit
selection still navigates to the exchange. Updates design system to
v0.1.18 for the new onSubmit prop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-27 23:33:39 +01:00
parent 004574d442
commit 1702200a60
5 changed files with 61 additions and 11 deletions

9
ui/package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "ui",
"version": "0.0.0",
"dependencies": {
"@cameleer/design-system": "^0.1.17",
"@cameleer/design-system": "^0.1.18",
"@tanstack/react-query": "^5.90.21",
"lucide-react": "^1.7.0",
"openapi-fetch": "^0.17.0",
@@ -277,10 +277,11 @@
}
},
"node_modules/@cameleer/design-system": {
"version": "0.1.17",
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.17/design-system-0.1.17.tgz",
"integrity": "sha512-THK6yN+xSrxEJadEQ4AZiVhPvoI2rq6gvmMonpxVhUw93dOPO5p06pRS5csJc1miFD1thOrazsoDzSTAbNaELw==",
"version": "0.1.18",
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.18/design-system-0.1.18.tgz",
"integrity": "sha512-uvGr4PFw6Eya+h9DSD0wBnzjIXhZpcndR2dDJX2tMvQqgy+32WTTTQ8BZZWZjOKLSv63UpBN/fwVSXtkA4dnqA==",
"dependencies": {
"lucide-react": "^1.7.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.0.0"

View File

@@ -14,7 +14,7 @@
"generate-api:live": "curl -s http://localhost:8081/api/v1/api-docs -o src/api/openapi.json && openapi-typescript src/api/openapi.json -o src/api/schema.d.ts"
},
"dependencies": {
"@cameleer/design-system": "^0.1.17",
"@cameleer/design-system": "^0.1.18",
"@tanstack/react-query": "^5.90.21",
"lucide-react": "^1.7.0",
"openapi-fetch": "^0.17.0",

View File

@@ -199,6 +199,14 @@ function LayoutContent() {
setPaletteOpen(false);
}, [navigate, setPaletteOpen]);
const handlePaletteSubmit = useCallback((query: string) => {
// Navigate to dashboard with full-text search applied
const currentPath = location.pathname;
// Stay on the current app/route context if we're already there
const basePath = currentPath.startsWith('/apps/') ? currentPath.split('/').slice(0, 4).join('/') : '/apps';
navigate(`${basePath}?text=${encodeURIComponent(query)}`);
}, [navigate, location.pathname]);
return (
<AppShell
sidebar={
@@ -217,6 +225,7 @@ function LayoutContent() {
onClose={() => setPaletteOpen(false)}
onOpen={() => setPaletteOpen(true)}
onSelect={handlePaletteSelect}
onSubmit={handlePaletteSubmit}
onQueryChange={setPaletteQuery}
data={searchData}
/>

View File

@@ -30,11 +30,34 @@
}
.tableTitle {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.clearSearch {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
margin-left: 4px;
border: none;
background: var(--bg-hover, #F5F0EA);
color: var(--text-secondary, #5C5347);
border-radius: 50%;
cursor: pointer;
padding: 0;
}
.clearSearch:hover {
background: var(--border, #E4DFD8);
color: var(--text-primary, #1A1612);
}
.tableRight {
display: flex;
align-items: center;

View File

@@ -1,6 +1,6 @@
import { useState, useMemo, useCallback } from 'react'
import { useParams, useNavigate } from 'react-router'
import { ExternalLink, AlertTriangle } from 'lucide-react'
import { useParams, useNavigate, useSearchParams } from 'react-router'
import { ExternalLink, AlertTriangle, X, Search } from 'lucide-react'
import {
DataTable,
DetailPanel,
@@ -196,6 +196,8 @@ const SHORTCUTS = [
export default function Dashboard() {
const { appId, routeId } = useParams<{ appId: string; routeId: string }>()
const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams()
const textFilter = searchParams.get('text') || undefined
const [selectedId, setSelectedId] = useState<string | undefined>()
const [panelOpen, setPanelOpen] = useState(false)
const [sortField, setSortField] = useState<string>('startTime')
@@ -226,12 +228,13 @@ export default function Dashboard() {
routeId: routeId || undefined,
application: appId || undefined,
status: statusParam,
text: textFilter,
sortField,
sortDir,
offset: 0,
limit: 50,
limit: textFilter ? 200 : 50,
},
true,
!textFilter,
)
const { data: detail } = useExecutionDetail(selectedId ?? null)
const { data: diagram } = useDiagramLayout(detail?.diagramContentHash ?? null)
@@ -398,12 +401,26 @@ export default function Dashboard() {
{/* Exchanges table */}
<div className={styles.tableSection}>
<div className={styles.tableHeader}>
<span className={styles.tableTitle}>Recent Exchanges</span>
<span className={styles.tableTitle}>
{textFilter ? (
<>
<Search size={14} style={{ marginRight: 4, verticalAlign: -2 }} />
Search: &ldquo;{textFilter}&rdquo;
<button
className={styles.clearSearch}
onClick={() => setSearchParams({})}
title="Clear search"
>
<X size={12} />
</button>
</>
) : 'Recent Exchanges'}
</span>
<div className={styles.tableRight}>
<span className={styles.tableMeta}>
{rows.length.toLocaleString()} of {(searchResult?.total ?? 0).toLocaleString()} exchanges
</span>
<Badge label="LIVE" color="success" />
{!textFilter && <Badge label="LIVE" color="success" />}
</div>
</div>