1
- import { useMemo , useState } from "react" ;
1
+ import { useCallback , useEffect , useMemo , useState } from "react" ;
2
2
3
3
import { useGetApplications } from "@squonk/data-manager-client/application" ;
4
4
import { useGetJobs } from "@squonk/data-manager-client/job" ;
5
5
6
6
import { withPageAuthRequired as withPageAuthRequiredCSR } from "@auth0/nextjs-auth0/client" ;
7
7
import { Alert , Container , Grid2 as Grid , MenuItem , TextField } from "@mui/material" ;
8
8
import groupBy from "just-group-by" ;
9
+ import { debounce } from "lodash-es" ;
9
10
import dynamic from "next/dynamic" ;
10
11
import Head from "next/head" ;
11
12
@@ -17,6 +18,7 @@ import { TEST_JOB_ID } from "../components/runCards/TestJob/jobId";
17
18
import { SearchTextField } from "../components/SearchTextField" ;
18
19
import { AS_ROLES , DM_ROLES } from "../constants/auth" ;
19
20
import { useCurrentProject , useIsUserAdminOrEditorOfCurrentProject } from "../hooks/projectHooks" ;
21
+ import { useKeyboardFocus } from "../hooks/useKeyboardFocus" ;
20
22
import Layout from "../layouts/Layout" ;
21
23
import { search } from "../utils/app/searches" ;
22
24
@@ -30,6 +32,20 @@ const TestJobCard = dynamic(
30
32
const Run = ( ) => {
31
33
const [ executionTypes , setExecutionTypes ] = useState ( [ "application" , "job" ] ) ;
32
34
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 ] ) ;
33
49
34
50
const currentProject = useCurrentProject ( ) ;
35
51
@@ -53,27 +69,41 @@ const Run = () => {
53
69
{ query : { select : ( data ) => data . jobs } } ,
54
70
) ;
55
71
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
+
56
99
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 ] ) => (
77
107
< Grid key = { key } size = { { md : 3 , sm : 6 , xs : 12 } } >
78
108
< JobCard disabled = { ! hasPermissionToRun } job = { jobs } projectId = { currentProject ?. project_id } />
79
109
</ Grid >
@@ -91,11 +121,10 @@ const Run = () => {
91
121
}
92
122
return jobCards ;
93
123
} , [
94
- applications ,
124
+ filteredApplications ,
125
+ filteredAndGroupedJobs ,
95
126
currentProject ?. project_id ,
96
127
executionTypes ,
97
- jobs ,
98
- searchValue ,
99
128
hasPermissionToRun ,
100
129
] ) ;
101
130
@@ -118,9 +147,7 @@ const Run = () => {
118
147
slotProps = { {
119
148
select : {
120
149
multiple : true ,
121
- onChange : ( event ) => {
122
- setExecutionTypes ( event . target . value as string [ ] ) ;
123
- } ,
150
+ onChange : handleExecutionTypesChange ,
124
151
} ,
125
152
} }
126
153
value = { executionTypes }
@@ -134,8 +161,9 @@ const Run = () => {
134
161
< Grid size = { { md : 4 , sm : 6 , xs : 12 } } sx = { { ml : "auto" } } >
135
162
< SearchTextField
136
163
fullWidth
164
+ ref = { inputRef }
137
165
value = { searchValue }
138
- onChange = { ( event ) => setSearchValue ( event . target . value ) }
166
+ onChange = { handleSearchChange }
139
167
/>
140
168
</ Grid >
141
169
</ Grid >
0 commit comments