Add sortable table columns, pre-populate date filter, inline command palette
- Table headers are now clickable to sort by column (client-side) - From date picker defaults to today 00:00 instead of empty - Command palette expands inline from search bar instead of modal dialog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,94 +1,11 @@
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useCommandPalette, type PaletteScope } from './use-command-palette';
|
||||
import { usePaletteSearch, type PaletteResult } from './use-palette-search';
|
||||
import { useExecutionSearch } from '../../pages/executions/use-execution-search';
|
||||
import { PaletteInput } from './PaletteInput';
|
||||
import { ScopeTabs } from './ScopeTabs';
|
||||
import { ResultsList } from './ResultsList';
|
||||
import { PaletteFooter } from './PaletteFooter';
|
||||
import type { ExecutionSummary, AgentInstance } from '../../api/schema';
|
||||
import styles from './CommandPalette.module.css';
|
||||
|
||||
const SCOPES: PaletteScope[] = ['all', 'executions', 'agents'];
|
||||
import { useEffect } from 'react';
|
||||
import { useCommandPalette } from './use-command-palette';
|
||||
|
||||
/**
|
||||
* Headless component: only registers the global Cmd+K / Ctrl+K keyboard shortcut.
|
||||
* The palette UI itself is rendered inline within SearchFilters.
|
||||
*/
|
||||
export function CommandPalette() {
|
||||
const { isOpen, close, scope, setScope, selectedIndex, setSelectedIndex, reset, filters } =
|
||||
useCommandPalette();
|
||||
const { results, executionCount, agentCount, isLoading } = usePaletteSearch();
|
||||
const execSearch = useExecutionSearch();
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(result: PaletteResult) => {
|
||||
if (result.type === 'execution') {
|
||||
const exec = result.data as ExecutionSummary;
|
||||
execSearch.setStatus(['COMPLETED', 'FAILED', 'RUNNING']);
|
||||
execSearch.setText(exec.executionId);
|
||||
execSearch.setRouteId('');
|
||||
execSearch.setAgentId('');
|
||||
execSearch.setProcessorType('');
|
||||
} else if (result.type === 'agent') {
|
||||
const agent = result.data as AgentInstance;
|
||||
execSearch.setStatus(['COMPLETED', 'FAILED', 'RUNNING']);
|
||||
execSearch.setAgentId(agent.id);
|
||||
execSearch.setText('');
|
||||
execSearch.setRouteId('');
|
||||
execSearch.setProcessorType('');
|
||||
}
|
||||
// Apply any active palette filters to the execution search
|
||||
for (const f of filters) {
|
||||
if (f.key === 'status') execSearch.setStatus([f.value.toUpperCase()]);
|
||||
if (f.key === 'route') execSearch.setRouteId(f.value);
|
||||
if (f.key === 'agent') execSearch.setAgentId(f.value);
|
||||
if (f.key === 'processor') execSearch.setProcessorType(f.value);
|
||||
}
|
||||
close();
|
||||
reset();
|
||||
},
|
||||
[close, reset, execSearch, filters],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (!isOpen) return;
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
close();
|
||||
reset();
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
setSelectedIndex(
|
||||
results.length > 0 ? (selectedIndex + 1) % results.length : 0,
|
||||
);
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
setSelectedIndex(
|
||||
results.length > 0
|
||||
? (selectedIndex - 1 + results.length) % results.length
|
||||
: 0,
|
||||
);
|
||||
break;
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
if (results[selectedIndex]) {
|
||||
handleSelect(results[selectedIndex]);
|
||||
}
|
||||
break;
|
||||
case 'Tab':
|
||||
e.preventDefault();
|
||||
const idx = SCOPES.indexOf(scope);
|
||||
setScope(SCOPES[(idx + 1) % SCOPES.length]);
|
||||
break;
|
||||
}
|
||||
},
|
||||
[isOpen, close, reset, selectedIndex, setSelectedIndex, results, handleSelect, scope, setScope],
|
||||
);
|
||||
|
||||
// Global Cmd+K / Ctrl+K listener
|
||||
useEffect(() => {
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
||||
@@ -106,28 +23,5 @@ export function CommandPalette() {
|
||||
return () => document.removeEventListener('keydown', onKeyDown);
|
||||
}, []);
|
||||
|
||||
// Keyboard handling when open
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => document.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleKeyDown]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return createPortal(
|
||||
<div className={styles.overlay} onClick={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
close();
|
||||
reset();
|
||||
}
|
||||
}}>
|
||||
<div className={styles.modal}>
|
||||
<PaletteInput />
|
||||
<ScopeTabs executionCount={executionCount} agentCount={agentCount} />
|
||||
<ResultsList results={results} isLoading={isLoading} onSelect={handleSelect} />
|
||||
<PaletteFooter />
|
||||
</div>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user