@@ -16,6 +16,7 @@ import {
1616 FolderGit2 ,
1717 GitPullRequestIcon ,
1818 TimerIcon ,
19+ BriefcaseIcon ,
1920} from "lucide-react" ;
2021import { ListPullRequest } from "@/lib/api/queries/pullRequests" ;
2122import { Tooltip , TooltipContent , TooltipTrigger } from "./ui/tooltip" ;
@@ -35,9 +36,13 @@ import {
3536 rememberLastProjectAtom ,
3637 buildRememberedTimerParams ,
3738} from "@/lib/milltime-preferences" ;
39+ import { SearchResult } from "@/lib/api/queries/search" ;
40+ import { useDebounce } from "@/hooks/useDebounce" ;
3841
3942export function CmdK ( ) {
4043 const [ open , setOpen ] = React . useState ( false ) ;
44+ const [ searchInput , setSearchInput ] = React . useState ( "" ) ;
45+ const debouncedSearchInput = useDebounce ( searchInput , 300 ) ;
4146
4247 const close = ( ) => setOpen ( false ) ;
4348
@@ -53,13 +58,27 @@ export function CmdK() {
5358 return ( ) => document . removeEventListener ( "keydown" , down ) ;
5459 } , [ ] ) ;
5560
61+ React . useEffect ( ( ) => {
62+ if ( ! open ) {
63+ setSearchInput ( "" ) ;
64+ }
65+ } , [ open ] ) ;
66+
5667 return (
5768 < CommandDialog open = { open } onOpenChange = { setOpen } >
58- < CommandInput placeholder = "Type a command or search..." />
69+ < CommandInput
70+ placeholder = "Type a command or search..."
71+ value = { searchInput }
72+ onValueChange = { setSearchInput }
73+ />
5974 < CommandList className = "max-w-2xl" >
6075 < CommandEmpty > No results found.</ CommandEmpty >
6176 < PagesCommandGroup close = { close } />
6277 < ActionsCommandGroup close = { close } />
78+ < SearchCommandGroup
79+ close = { close }
80+ searchQuery = { debouncedSearchInput }
81+ />
6382 < PRCommandGroup close = { close } />
6483 </ CommandList >
6584 </ CommandDialog >
@@ -218,7 +237,7 @@ function ActionsCommandGroup(props: { close: () => void }) {
218237function PRCommandGroup ( props : { close : ( ) => void } ) {
219238 const navigate = useNavigate ( ) ;
220239
221- const { data : pullRequests } = useQuery ( queries . listPullRequests ( ) ) ;
240+ const { data : pullRequests } = useQuery ( queries . pullRequests . listPullRequests ( ) ) ;
222241
223242 return (
224243 < CommandGroup heading = "Pull requests" >
@@ -262,3 +281,64 @@ function PRCommandGroup(props: { close: () => void }) {
262281function pullRequestValue ( pr : ListPullRequest ) {
263282 return `!${ pr . id } ${ pr . title } ${ pr . repoName } ${ pr . createdBy . displayName } ${ pr . workItems . map ( ( wi ) => `#${ wi . id } ` ) . join ( " " ) } ` ;
264283}
284+
285+ function SearchCommandGroup ( props : {
286+ close : ( ) => void ;
287+ searchQuery : string ;
288+ } ) {
289+ const navigate = useNavigate ( ) ;
290+
291+ const { data : searchResults } = useQuery (
292+ queries . search . search ( props . searchQuery , 10 )
293+ ) ;
294+
295+ if ( ! props . searchQuery || ! searchResults || searchResults . length === 0 ) {
296+ return null ;
297+ }
298+
299+ return (
300+ < CommandGroup heading = "Search results" >
301+ { searchResults . map ( ( result ) => (
302+ < CommandItem
303+ key = { `${ result . sourceType } -${ result . externalId } ` }
304+ value = { searchResultValue ( result ) }
305+ onSelect = { ( ) => {
306+ if ( result . sourceType === "Pr" ) {
307+ navigate ( {
308+ to : "/prs/$prId" ,
309+ params : { prId : result . externalId . toString ( ) } ,
310+ } ) ;
311+ } else {
312+ window . open ( result . url , "_blank" , "noopener,noreferrer" ) ;
313+ }
314+ props . close ( ) ;
315+ } }
316+ >
317+ < div className = "flex w-full flex-row items-center justify-between gap-2 truncate" >
318+ < div className = "flex max-w-[75%] flex-row items-center gap-2" >
319+ { result . sourceType === "Pr" ? (
320+ < GitPullRequestIcon className = "h-4 w-4 text-muted-foreground" />
321+ ) : (
322+ < BriefcaseIcon className = "h-4 w-4 text-muted-foreground" />
323+ ) }
324+ < span className = "text-muted-foreground" >
325+ { result . sourceType === "Pr" ? "!" : "#" }
326+ { result . externalId }
327+ </ span >
328+ < span className = "truncate" > { result . title } </ span >
329+ </ div >
330+ { result . authorName && (
331+ < span className = "text-xs text-muted-foreground" >
332+ { result . authorName }
333+ </ span >
334+ ) }
335+ </ div >
336+ </ CommandItem >
337+ ) ) }
338+ </ CommandGroup >
339+ ) ;
340+ }
341+
342+ function searchResultValue ( result : SearchResult ) {
343+ return `${ result . sourceType === "Pr" ? "!" : "#" } ${ result . externalId } ${ result . title } ${ result . authorName ?? "" } ` ;
344+ }
0 commit comments