@@ -7,20 +7,19 @@ import { getDuration } from '@/utils/text';
7
7
import { Modal , Select , Table , TagInput } from '@douyinfe/semi-ui' ;
8
8
import { ColumnProps } from '@douyinfe/semi-ui/lib/es/table' ;
9
9
import { Button } from '@nextui-org/react' ;
10
- import { useQuery } from '@tanstack/react-query' ;
10
+ import { useInfiniteQuery } from '@tanstack/react-query' ;
11
11
import { createFileRoute , Link , useNavigate } from '@tanstack/react-router' ;
12
12
import _ from 'lodash' ;
13
13
import { TasksQuery , Task , TaskTypes , TaskStatus } from 'meilisearch' ;
14
- import { useEffect , useReducer } from 'react' ;
14
+ import { useEffect , useMemo , useReducer } from 'react' ;
15
15
import { useTranslation } from 'react-i18next' ;
16
16
import ReactJson from 'react-json-view' ;
17
17
import { z } from 'zod' ;
18
18
19
19
const searchSchema = z
20
20
. object ( {
21
21
indexUids : z . string ( ) . array ( ) . optional ( ) ,
22
- limit : z . number ( ) . positive ( ) . optional ( ) ,
23
- from : z . number ( ) . nonnegative ( ) . optional ( ) ,
22
+ limit : z . number ( ) . positive ( ) . optional ( ) . default ( 20 ) ,
24
23
statuses : z . string ( ) . array ( ) . optional ( ) ,
25
24
types : z . string ( ) . array ( ) . optional ( ) ,
26
25
} )
@@ -53,15 +52,27 @@ const Page = () => {
53
52
} ) ;
54
53
} , [ navigate , state ] ) ;
55
54
56
- const query = useQuery ( {
55
+ // @ts -expect-error
56
+ const query = useInfiniteQuery ( {
57
57
queryKey : [ 'tasks' , host , state ] ,
58
- queryFn : async ( ) => {
58
+ queryFn : async ( { pageParam } : { pageParam : { limit : number ; from ?: number } } ) => {
59
59
showRequestLoader ( ) ;
60
60
console . debug ( 'getTasks' , client . config , state ) ;
61
61
return await client . getTasks ( {
62
62
..._ . omitBy ( state , _ . isEmpty ) ,
63
+ from : pageParam . from ,
64
+ limit : pageParam . limit ,
63
65
} ) ;
64
66
} ,
67
+ initialPageParam : {
68
+ limit : state . limit ,
69
+ } ,
70
+ getNextPageParam : ( lastPage ) => {
71
+ return {
72
+ limit : lastPage . limit ,
73
+ from : lastPage . next ,
74
+ } ;
75
+ } ,
65
76
} ) ;
66
77
67
78
useEffect ( ( ) => {
@@ -73,142 +84,162 @@ const Page = () => {
73
84
}
74
85
} , [ query . error , query . isError , query . isFetching ] ) ;
75
86
76
- const columns : ColumnProps < Task > [ ] = [
77
- {
78
- title : 'UID' ,
79
- dataIndex : 'uid' ,
80
- width : 100 ,
81
- } ,
82
- {
83
- title : t ( 'indexes' ) ,
84
- dataIndex : 'indexUid' ,
85
- render : ( val ) => ( val ? < Link to = { `/ins/${ currentInstance . id } /index/${ val } ` } > { val } </ Link > : '-' ) ,
86
- } ,
87
- {
88
- title : t ( 'common:type' ) ,
89
- dataIndex : 'type' ,
90
- render : ( _ ) => t ( `type.${ _ } ` ) ,
91
- } ,
92
- {
93
- title : t ( 'common:status' ) ,
94
- dataIndex : 'status' ,
95
- width : 120 ,
96
- render : ( _ ) => t ( `status.${ _ } ` ) ,
97
- } ,
98
- {
99
- title : t ( 'duration' ) ,
100
- dataIndex : 'duration' ,
101
- width : 200 ,
102
- render : ( _ , item ) => {
103
- if ( ! item . duration ) {
104
- return '-' ;
105
- }
87
+ const list = useMemo ( ( ) => {
88
+ return query . data ?. pages . map ( ( page ) => page . results ) . reduce ( ( acc , cur ) => acc . concat ( cur ) , [ ] ) || [ ] ;
89
+ } , [ query . data ?. pages ] ) ;
106
90
107
- return `${ getDuration ( item . duration , 'millisecond' ) } ms` ;
91
+ const columns : ColumnProps < Task > [ ] = useMemo (
92
+ ( ) => [
93
+ {
94
+ title : 'UID' ,
95
+ dataIndex : 'uid' ,
96
+ width : 100 ,
108
97
} ,
109
- } ,
110
- {
111
- title : t ( 'enqueued_at' ) ,
112
- dataIndex : 'enqueuedAt' ,
113
- width : 220 ,
114
- render : ( _ , item ) => {
115
- return < TimeAgo date = { item . enqueuedAt } /> ;
98
+ {
99
+ title : t ( 'indexes' ) ,
100
+ dataIndex : 'indexUid' ,
101
+ render : ( val ) => ( val ? < Link to = { `/ins/${ currentInstance . id } /index/${ val } ` } > { val } </ Link > : '-' ) ,
116
102
} ,
117
- } ,
118
- {
119
- title : t ( 'started_at' ) ,
120
- dataIndex : 'startedAt' ,
121
- width : 220 ,
122
- render : ( _ , item ) => {
123
- return < TimeAgo date = { item . startedAt } /> ;
103
+ {
104
+ title : t ( 'common:type' ) ,
105
+ dataIndex : 'type' ,
106
+ render : ( _ ) => t ( `type.${ _ } ` ) ,
124
107
} ,
125
- } ,
126
- {
127
- title : t ( 'finished_at' ) ,
128
- dataIndex : 'finishedAt' ,
129
- width : 220 ,
130
- render : ( _ , item ) => {
131
- return < TimeAgo date = { item . finishedAt } /> ;
108
+ {
109
+ title : t ( 'common:status' ) ,
110
+ dataIndex : 'status' ,
111
+ width : 120 ,
112
+ render : ( _ ) => t ( `status.${ _ } ` ) ,
132
113
} ,
133
- } ,
134
- {
135
- title : t ( 'actions' ) ,
136
- fixed : 'right' ,
137
- width : 150 ,
138
- render : ( _ , record ) => (
139
- < div className = "flex justify-center items-center gap-2" >
140
- < Button
141
- size = "sm"
142
- onClick = { ( ) => {
143
- Modal . info ( {
144
- title : t ( 'common:detail' ) ,
145
- centered : true ,
146
- footer : null ,
147
- content : (
148
- < div className = "flex justify-center items-center p-4 pb-6" >
149
- < ReactJson
150
- name = { false }
151
- displayDataTypes = { false }
152
- displayObjectSize = { false }
153
- enableClipboard = { false }
154
- src = { record }
155
- collapsed = { 3 }
156
- collapseStringsAfterLength = { 50 }
157
- />
158
- </ div >
159
- ) ,
160
- } ) ;
161
- } }
162
- variant = "flat"
163
- >
164
- { t ( 'common:detail' ) }
165
- </ Button >
166
- </ div >
167
- ) ,
168
- } ,
169
- ] ;
114
+ {
115
+ title : t ( 'duration' ) ,
116
+ dataIndex : 'duration' ,
117
+ width : 200 ,
118
+ render : ( _ , item ) => {
119
+ if ( ! item . duration ) {
120
+ return '-' ;
121
+ }
170
122
171
- return (
172
- < div className = "flex-1 overflow-scroll max-h-fit" >
173
- < main className = "p-4 flex flex-col gap-4" >
174
- < div className = { `flex justify-end items-center gap-4` } >
175
- < TagInput
176
- className = "flex-1"
177
- placeholder = { t ( 'filter.index.placeholder' ) }
178
- value = { state . indexUids }
179
- onChange = { ( value ) => {
180
- updateState ( { indexUids : value } ) ;
181
- } }
182
- />
183
- < Select
184
- placeholder = { t ( 'filter.type.placeholder' ) }
185
- optionList = { _ . entries ( t ( 'type' , { returnObjects : true } ) as Record < string , string > ) . map ( ( [ k , v ] ) => ( {
186
- value : k ,
187
- label : v ,
188
- } ) ) }
189
- multiple
190
- value = { state . types }
191
- onChange = { ( value ) => {
192
- updateState ( { types : ( value as TaskTypes [ ] ) || undefined } ) ;
193
- } }
194
- />
195
- < Select
196
- placeholder = { t ( 'filter.status.placeholder' ) }
197
- optionList = { _ . entries ( t ( 'status' , { returnObjects : true } ) as Record < string , string > ) . map ( ( [ k , v ] ) => ( {
198
- value : k ,
199
- label : v ,
200
- } ) ) }
201
- multiple
202
- value = { state . statuses }
203
- onChange = { ( value ) => {
204
- updateState ( { statuses : ( value as TaskStatus [ ] ) || undefined } ) ;
205
- } }
206
- />
207
- </ div >
123
+ return `${ getDuration ( item . duration , 'millisecond' ) } ms` ;
124
+ } ,
125
+ } ,
126
+ {
127
+ title : t ( 'enqueued_at' ) ,
128
+ dataIndex : 'enqueuedAt' ,
129
+ width : 220 ,
130
+ render : ( _ , item ) => {
131
+ return < TimeAgo date = { item . enqueuedAt } /> ;
132
+ } ,
133
+ } ,
134
+ {
135
+ title : t ( 'started_at' ) ,
136
+ dataIndex : 'startedAt' ,
137
+ width : 220 ,
138
+ render : ( _ , item ) => {
139
+ return < TimeAgo date = { item . startedAt } /> ;
140
+ } ,
141
+ } ,
142
+ {
143
+ title : t ( 'finished_at' ) ,
144
+ dataIndex : 'finishedAt' ,
145
+ width : 220 ,
146
+ render : ( _ , item ) => {
147
+ return < TimeAgo date = { item . finishedAt } /> ;
148
+ } ,
149
+ } ,
150
+ {
151
+ title : t ( 'actions' ) ,
152
+ fixed : 'right' ,
153
+ width : 150 ,
154
+ render : ( _ , record ) => (
155
+ < div className = "flex justify-center items-center gap-2" >
156
+ < Button
157
+ size = "sm"
158
+ onClick = { ( ) => {
159
+ Modal . info ( {
160
+ title : t ( 'common:detail' ) ,
161
+ centered : true ,
162
+ footer : null ,
163
+ content : (
164
+ < div className = "flex justify-center items-center p-4 pb-6" >
165
+ < ReactJson
166
+ name = { false }
167
+ displayDataTypes = { false }
168
+ displayObjectSize = { false }
169
+ enableClipboard = { false }
170
+ src = { record }
171
+ collapsed = { 3 }
172
+ collapseStringsAfterLength = { 50 }
173
+ />
174
+ </ div >
175
+ ) ,
176
+ } ) ;
177
+ } }
178
+ variant = "flat"
179
+ >
180
+ { t ( 'common:detail' ) }
181
+ </ Button >
182
+ </ div >
183
+ ) ,
184
+ } ,
185
+ ] ,
186
+ [ currentInstance . id , t ]
187
+ ) ;
208
188
209
- < Table columns = { columns } dataSource = { query . data ?. results } pagination = { false } empty = { t ( 'empty' ) } />
210
- </ main >
211
- </ div >
189
+ return useMemo (
190
+ ( ) => (
191
+ < div className = "flex-1 max-h-fit overflow-hidden" >
192
+ < main className = "flex flex-col gap-4 h-full" >
193
+ < div className = { `p-4 flex justify-end items-center gap-4 sticky top-0 z-10 bg-white` } >
194
+ < TagInput
195
+ className = "flex-1"
196
+ placeholder = { t ( 'filter.index.placeholder' ) }
197
+ value = { state . indexUids }
198
+ onChange = { ( value ) => {
199
+ updateState ( { indexUids : value } ) ;
200
+ } }
201
+ />
202
+ < Select
203
+ placeholder = { t ( 'filter.type.placeholder' ) }
204
+ optionList = { _ . entries ( t ( 'type' , { returnObjects : true } ) as Record < string , string > ) . map ( ( [ k , v ] ) => ( {
205
+ value : k ,
206
+ label : v ,
207
+ } ) ) }
208
+ multiple
209
+ value = { state . types }
210
+ onChange = { ( value ) => {
211
+ updateState ( { types : ( value as TaskTypes [ ] ) || undefined } ) ;
212
+ } }
213
+ />
214
+ < Select
215
+ placeholder = { t ( 'filter.status.placeholder' ) }
216
+ optionList = { _ . entries ( t ( 'status' , { returnObjects : true } ) as Record < string , string > ) . map ( ( [ k , v ] ) => ( {
217
+ value : k ,
218
+ label : v ,
219
+ } ) ) }
220
+ multiple
221
+ value = { state . statuses }
222
+ onChange = { ( value ) => {
223
+ updateState ( { statuses : ( value as TaskStatus [ ] ) || undefined } ) ;
224
+ } }
225
+ />
226
+ </ div >
227
+
228
+ < div
229
+ className = "p-2 overflow-scroll"
230
+ onScroll = { ( e ) => {
231
+ const { scrollTop, clientHeight, scrollHeight } = e . target ;
232
+ if ( Math . abs ( scrollHeight - ( scrollTop + clientHeight ) ) <= 1 ) {
233
+ query . fetchNextPage ( ) ;
234
+ }
235
+ } }
236
+ >
237
+ < Table columns = { columns } dataSource = { list } pagination = { false } empty = { t ( 'empty' ) } />
238
+ </ div >
239
+ </ main >
240
+ </ div >
241
+ ) ,
242
+ [ t , state . indexUids , state . types , state . statuses , columns , list , query ]
212
243
) ;
213
244
} ;
214
245
0 commit comments