@@ -85,6 +85,8 @@ export function ScheduledTasks() {
8585 direction : 'asc' | 'desc'
8686 } | null > ( null )
8787 const [ scheduleTypeFilter , setScheduleTypeFilter ] = useState < 'all' | 'recurring' | 'once' > ( 'all' )
88+ const [ statusFilter , setStatusFilter ] = useState < 'all' | 'active' | 'paused' > ( 'all' )
89+ const [ healthFilter , setHealthFilter ] = useState < 'all' | 'has-failures' > ( 'all' )
8890
8991 const visibleItems = useMemo (
9092 ( ) => allItems . filter ( ( item ) => item . sourceType === 'job' && item . status !== 'completed' ) ,
@@ -108,6 +110,16 @@ export function ScheduledTasks() {
108110 )
109111 }
110112
113+ if ( statusFilter !== 'all' ) {
114+ result = result . filter ( ( item ) =>
115+ statusFilter === 'active' ? item . status === 'active' : item . status === 'disabled'
116+ )
117+ }
118+
119+ if ( healthFilter === 'has-failures' ) {
120+ result = result . filter ( ( item ) => ( item . failedCount ?? 0 ) > 0 )
121+ }
122+
111123 const col = activeSort ?. column ?? 'nextRun'
112124 const dir = activeSort ?. direction ?? 'desc'
113125 return [ ...result ] . sort ( ( a , b ) => {
@@ -129,7 +141,14 @@ export function ScheduledTasks() {
129141 }
130142 return dir === 'asc' ? cmp : - cmp
131143 } )
132- } , [ visibleItems , debouncedSearchQuery , scheduleTypeFilter , activeSort ] )
144+ } , [
145+ visibleItems ,
146+ debouncedSearchQuery ,
147+ scheduleTypeFilter ,
148+ statusFilter ,
149+ healthFilter ,
150+ activeSort ,
151+ ] )
133152
134153 const rows : ResourceRow [ ] = useMemo (
135154 ( ) =>
@@ -246,21 +265,78 @@ export function ScheduledTasks() {
246265 </ button >
247266 ) ) }
248267 </ div >
268+ < div className = 'border-[var(--border-1)] border-t border-b px-3 py-2' >
269+ < span className = 'font-medium text-[var(--text-secondary)] text-caption' > Status</ span >
270+ </ div >
271+ < div className = 'flex flex-col gap-0.5 px-3 py-2' >
272+ { (
273+ [
274+ { value : 'all' , label : 'All' } ,
275+ { value : 'active' , label : 'Active' } ,
276+ { value : 'paused' , label : 'Paused' } ,
277+ ] as const
278+ ) . map ( ( { value, label } ) => (
279+ < button
280+ key = { value }
281+ type = 'button'
282+ className = { cn (
283+ 'flex w-full cursor-pointer select-none items-center rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-secondary)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-active)]' ,
284+ statusFilter === value && 'bg-[var(--surface-active)]'
285+ ) }
286+ onClick = { ( ) => setStatusFilter ( value ) }
287+ >
288+ { label }
289+ </ button >
290+ ) ) }
291+ </ div >
292+ < div className = 'border-[var(--border-1)] border-t border-b px-3 py-2' >
293+ < span className = 'font-medium text-[var(--text-secondary)] text-caption' > Health</ span >
294+ </ div >
295+ < div className = 'flex flex-col gap-0.5 px-3 py-2' >
296+ { (
297+ [
298+ { value : 'all' , label : 'All' } ,
299+ { value : 'has-failures' , label : 'Has failures' } ,
300+ ] as const
301+ ) . map ( ( { value, label } ) => (
302+ < button
303+ key = { value }
304+ type = 'button'
305+ className = { cn (
306+ 'flex w-full cursor-pointer select-none items-center rounded-[5px] px-2 py-[5px] font-medium text-[var(--text-secondary)] text-caption outline-none transition-colors hover-hover:bg-[var(--surface-active)]' ,
307+ healthFilter === value && 'bg-[var(--surface-active)]'
308+ ) }
309+ onClick = { ( ) => setHealthFilter ( value ) }
310+ >
311+ { label }
312+ </ button >
313+ ) ) }
314+ </ div >
249315 </ div >
250316 )
251317
252- const filterTags : FilterTag [ ] = useMemo (
253- ( ) =>
254- scheduleTypeFilter === 'all'
255- ? [ ]
256- : [
257- {
258- label : scheduleTypeFilter === 'recurring' ? 'Type: Recurring' : 'Type: One-time' ,
259- onRemove : ( ) => setScheduleTypeFilter ( 'all' ) ,
260- } ,
261- ] ,
262- [ scheduleTypeFilter ]
263- )
318+ const filterTags : FilterTag [ ] = useMemo ( ( ) => {
319+ const tags : FilterTag [ ] = [ ]
320+ if ( scheduleTypeFilter !== 'all' ) {
321+ tags . push ( {
322+ label : scheduleTypeFilter === 'recurring' ? 'Type: Recurring' : 'Type: One-time' ,
323+ onRemove : ( ) => setScheduleTypeFilter ( 'all' ) ,
324+ } )
325+ }
326+ if ( statusFilter !== 'all' ) {
327+ tags . push ( {
328+ label : statusFilter === 'active' ? 'Status: Active' : 'Status: Paused' ,
329+ onRemove : ( ) => setStatusFilter ( 'all' ) ,
330+ } )
331+ }
332+ if ( healthFilter === 'has-failures' ) {
333+ tags . push ( {
334+ label : 'Health: Has failures' ,
335+ onRemove : ( ) => setHealthFilter ( 'all' ) ,
336+ } )
337+ }
338+ return tags
339+ } , [ scheduleTypeFilter , statusFilter , healthFilter ] )
264340
265341 return (
266342 < >
0 commit comments