feat: Cmd+K Enter applies full-text search to dashboard
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:
9
ui/package-lock.json
generated
9
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "ui",
|
"name": "ui",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.1.17",
|
"@cameleer/design-system": "^0.1.18",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
@@ -277,10 +277,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@cameleer/design-system": {
|
"node_modules/@cameleer/design-system": {
|
||||||
"version": "0.1.17",
|
"version": "0.1.18",
|
||||||
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.17/design-system-0.1.17.tgz",
|
"resolved": "https://gitea.siegeln.net/api/packages/cameleer/npm/%40cameleer%2Fdesign-system/-/0.1.18/design-system-0.1.18.tgz",
|
||||||
"integrity": "sha512-THK6yN+xSrxEJadEQ4AZiVhPvoI2rq6gvmMonpxVhUw93dOPO5p06pRS5csJc1miFD1thOrazsoDzSTAbNaELw==",
|
"integrity": "sha512-uvGr4PFw6Eya+h9DSD0wBnzjIXhZpcndR2dDJX2tMvQqgy+32WTTTQ8BZZWZjOKLSv63UpBN/fwVSXtkA4dnqA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"lucide-react": "^1.7.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-router-dom": "^7.0.0"
|
"react-router-dom": "^7.0.0"
|
||||||
|
|||||||
@@ -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"
|
"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": {
|
"dependencies": {
|
||||||
"@cameleer/design-system": "^0.1.17",
|
"@cameleer/design-system": "^0.1.18",
|
||||||
"@tanstack/react-query": "^5.90.21",
|
"@tanstack/react-query": "^5.90.21",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.7.0",
|
||||||
"openapi-fetch": "^0.17.0",
|
"openapi-fetch": "^0.17.0",
|
||||||
|
|||||||
@@ -199,6 +199,14 @@ function LayoutContent() {
|
|||||||
setPaletteOpen(false);
|
setPaletteOpen(false);
|
||||||
}, [navigate, setPaletteOpen]);
|
}, [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 (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
sidebar={
|
sidebar={
|
||||||
@@ -217,6 +225,7 @@ function LayoutContent() {
|
|||||||
onClose={() => setPaletteOpen(false)}
|
onClose={() => setPaletteOpen(false)}
|
||||||
onOpen={() => setPaletteOpen(true)}
|
onOpen={() => setPaletteOpen(true)}
|
||||||
onSelect={handlePaletteSelect}
|
onSelect={handlePaletteSelect}
|
||||||
|
onSubmit={handlePaletteSubmit}
|
||||||
onQueryChange={setPaletteQuery}
|
onQueryChange={setPaletteQuery}
|
||||||
data={searchData}
|
data={searchData}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -30,11 +30,34 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tableTitle {
|
.tableTitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
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 {
|
.tableRight {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo, useCallback } from 'react'
|
import { useState, useMemo, useCallback } from 'react'
|
||||||
import { useParams, useNavigate } from 'react-router'
|
import { useParams, useNavigate, useSearchParams } from 'react-router'
|
||||||
import { ExternalLink, AlertTriangle } from 'lucide-react'
|
import { ExternalLink, AlertTriangle, X, Search } from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
DataTable,
|
DataTable,
|
||||||
DetailPanel,
|
DetailPanel,
|
||||||
@@ -196,6 +196,8 @@ const SHORTCUTS = [
|
|||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const { appId, routeId } = useParams<{ appId: string; routeId: string }>()
|
const { appId, routeId } = useParams<{ appId: string; routeId: string }>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
const textFilter = searchParams.get('text') || undefined
|
||||||
const [selectedId, setSelectedId] = useState<string | undefined>()
|
const [selectedId, setSelectedId] = useState<string | undefined>()
|
||||||
const [panelOpen, setPanelOpen] = useState(false)
|
const [panelOpen, setPanelOpen] = useState(false)
|
||||||
const [sortField, setSortField] = useState<string>('startTime')
|
const [sortField, setSortField] = useState<string>('startTime')
|
||||||
@@ -226,12 +228,13 @@ export default function Dashboard() {
|
|||||||
routeId: routeId || undefined,
|
routeId: routeId || undefined,
|
||||||
application: appId || undefined,
|
application: appId || undefined,
|
||||||
status: statusParam,
|
status: statusParam,
|
||||||
|
text: textFilter,
|
||||||
sortField,
|
sortField,
|
||||||
sortDir,
|
sortDir,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: 50,
|
limit: textFilter ? 200 : 50,
|
||||||
},
|
},
|
||||||
true,
|
!textFilter,
|
||||||
)
|
)
|
||||||
const { data: detail } = useExecutionDetail(selectedId ?? null)
|
const { data: detail } = useExecutionDetail(selectedId ?? null)
|
||||||
const { data: diagram } = useDiagramLayout(detail?.diagramContentHash ?? null)
|
const { data: diagram } = useDiagramLayout(detail?.diagramContentHash ?? null)
|
||||||
@@ -398,12 +401,26 @@ export default function Dashboard() {
|
|||||||
{/* Exchanges table */}
|
{/* Exchanges table */}
|
||||||
<div className={styles.tableSection}>
|
<div className={styles.tableSection}>
|
||||||
<div className={styles.tableHeader}>
|
<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: “{textFilter}”
|
||||||
|
<button
|
||||||
|
className={styles.clearSearch}
|
||||||
|
onClick={() => setSearchParams({})}
|
||||||
|
title="Clear search"
|
||||||
|
>
|
||||||
|
<X size={12} />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : 'Recent Exchanges'}
|
||||||
|
</span>
|
||||||
<div className={styles.tableRight}>
|
<div className={styles.tableRight}>
|
||||||
<span className={styles.tableMeta}>
|
<span className={styles.tableMeta}>
|
||||||
{rows.length.toLocaleString()} of {(searchResult?.total ?? 0).toLocaleString()} exchanges
|
{rows.length.toLocaleString()} of {(searchResult?.total ?? 0).toLocaleString()} exchanges
|
||||||
</span>
|
</span>
|
||||||
<Badge label="LIVE" color="success" />
|
{!textFilter && <Badge label="LIVE" color="success" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user