1- import { useMemo , useState } from "react" ;
1+ import { useCallback , useEffect , useMemo , useState } from "react" ;
22
33import { useGetApplications } from "@squonk/data-manager-client/application" ;
44import { useGetJobs } from "@squonk/data-manager-client/job" ;
55
66import { withPageAuthRequired as withPageAuthRequiredCSR } from "@auth0/nextjs-auth0/client" ;
77import { Alert , Container , Grid2 as Grid , MenuItem , TextField } from "@mui/material" ;
88import groupBy from "just-group-by" ;
9+ import { debounce } from "lodash-es" ;
910import dynamic from "next/dynamic" ;
1011import Head from "next/head" ;
1112
@@ -17,6 +18,7 @@ import { TEST_JOB_ID } from "../components/runCards/TestJob/jobId";
1718import { SearchTextField } from "../components/SearchTextField" ;
1819import { AS_ROLES , DM_ROLES } from "../constants/auth" ;
1920import { useCurrentProject , useIsUserAdminOrEditorOfCurrentProject } from "../hooks/projectHooks" ;
21+ import { useKeyboardFocus } from "../hooks/useKeyboardFocus" ;
2022import Layout from "../layouts/Layout" ;
2123import { search } from "../utils/app/searches" ;
2224
@@ -30,6 +32,20 @@ const TestJobCard = dynamic(
3032const Run = ( ) => {
3133 const [ executionTypes , setExecutionTypes ] = useState ( [ "application" , "job" ] ) ;
3234 const [ searchValue , setSearchValue ] = useState ( "" ) ;
35+ const [ debouncedSearchValue , setDebouncedSearchValue ] = useState ( "" ) ;
36+ const inputRef = useKeyboardFocus ( ) ;
37+
38+ // Create debounced search function
39+ const debouncedSetSearch = useMemo (
40+ ( ) => debounce ( ( value : string ) => setDebouncedSearchValue ( value ) , 300 ) ,
41+ [ ]
42+ ) ;
43+
44+ // Update debounced value when search value changes
45+ useEffect ( ( ) => {
46+ debouncedSetSearch ( searchValue ) ;
47+ return ( ) => debouncedSetSearch . cancel ( ) ;
48+ } , [ searchValue , debouncedSetSearch ] ) ;
3349
3450 const currentProject = useCurrentProject ( ) ;
3551
@@ -53,27 +69,41 @@ const Run = () => {
5369 { query : { select : ( data ) => data . jobs } } ,
5470 ) ;
5571
72+ // Memoize filtered applications
73+ const filteredApplications = useMemo ( ( ) => {
74+ if ( ! applications ) { return [ ] ; }
75+ return applications . filter ( ( { kind } ) => search ( [ kind ] , debouncedSearchValue ) ) ;
76+ } , [ applications , debouncedSearchValue ] ) ;
77+
78+ // Memoize filtered and grouped jobs
79+ const filteredAndGroupedJobs = useMemo ( ( ) => {
80+ if ( ! jobs ) { return { } ; }
81+ const filteredJobs = jobs
82+ . filter ( ( { keywords, category, name, job, description } ) =>
83+ search ( [ keywords , category , name , job , description ] , debouncedSearchValue ) ,
84+ )
85+ . filter ( job => ! job . replaced_by ) ;
86+
87+ return groupBy ( filteredJobs , ( job ) => `${ job . collection } +${ job . job } ` ) ;
88+ } , [ jobs , debouncedSearchValue ] ) ;
89+
90+ // Memoize event handlers
91+ const handleSearchChange = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
92+ setSearchValue ( event . target . value ) ;
93+ } , [ ] ) ;
94+
95+ const handleExecutionTypesChange = useCallback ( ( event : any ) => {
96+ setExecutionTypes ( event . target . value as string [ ] ) ;
97+ } , [ ] ) ;
98+
5699 const cards = useMemo ( ( ) => {
57- const applicationCards =
58- applications
59- // Filter the apps by the search value
60- ?. filter ( ( { kind } ) => search ( [ kind ] , searchValue ) )
61- // Then create a card for each
62- . map ( ( app ) => (
63- < Grid key = { app . application_id } size = { { md : 3 , sm : 6 , xs : 12 } } >
64- < ApplicationCard app = { app } projectId = { currentProject ?. project_id } />
65- </ Grid >
66- ) ) ?? [ ] ;
67-
68- // Filter the apps by the search value
69- const filteredJobs = ( jobs ?? [ ] ) . filter ( ( { keywords, category, name, job, description } ) =>
70- search ( [ keywords , category , name , job , description ] , searchValue ) ,
71- ) . filter ( job => ! job . replaced_by ) ;
72-
73- const groupedJobObjects = groupBy ( filteredJobs , ( job ) => `${ job . collection } +${ job . job } ` )
74-
75- // Then create a card for each
76- const jobCards = Object . entries ( groupedJobObjects ) . map ( ( [ key , jobs ] ) => (
100+ const applicationCards = filteredApplications . map ( ( app ) => (
101+ < Grid key = { app . application_id } size = { { md : 3 , sm : 6 , xs : 12 } } >
102+ < ApplicationCard app = { app } projectId = { currentProject ?. project_id } />
103+ </ Grid >
104+ ) ) ;
105+
106+ const jobCards = Object . entries ( filteredAndGroupedJobs ) . map ( ( [ key , jobs ] ) => (
77107 < Grid key = { key } size = { { md : 3 , sm : 6 , xs : 12 } } >
78108 < JobCard disabled = { ! hasPermissionToRun } job = { jobs } projectId = { currentProject ?. project_id } />
79109 </ Grid >
@@ -91,11 +121,10 @@ const Run = () => {
91121 }
92122 return jobCards ;
93123 } , [
94- applications ,
124+ filteredApplications ,
125+ filteredAndGroupedJobs ,
95126 currentProject ?. project_id ,
96127 executionTypes ,
97- jobs ,
98- searchValue ,
99128 hasPermissionToRun ,
100129 ] ) ;
101130
@@ -118,9 +147,7 @@ const Run = () => {
118147 slotProps = { {
119148 select : {
120149 multiple : true ,
121- onChange : ( event ) => {
122- setExecutionTypes ( event . target . value as string [ ] ) ;
123- } ,
150+ onChange : handleExecutionTypesChange ,
124151 } ,
125152 } }
126153 value = { executionTypes }
@@ -134,8 +161,9 @@ const Run = () => {
134161 < Grid size = { { md : 4 , sm : 6 , xs : 12 } } sx = { { ml : "auto" } } >
135162 < SearchTextField
136163 fullWidth
164+ ref = { inputRef }
137165 value = { searchValue }
138- onChange = { ( event ) => setSearchValue ( event . target . value ) }
166+ onChange = { handleSearchChange }
139167 />
140168 </ Grid >
141169 </ Grid >
0 commit comments