@@ -26,6 +26,8 @@ export function InstalledScriptsTab() {
26
26
const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
27
27
const [ statusFilter , setStatusFilter ] = useState < 'all' | 'success' | 'failed' | 'in_progress' > ( 'all' ) ;
28
28
const [ serverFilter , setServerFilter ] = useState < string > ( 'all' ) ;
29
+ const [ sortField , setSortField ] = useState < 'script_name' | 'container_id' | 'server_name' | 'status' | 'installation_date' > ( 'script_name' ) ;
30
+ const [ sortDirection , setSortDirection ] = useState < 'asc' | 'desc' > ( 'asc' ) ;
29
31
const [ updatingScript , setUpdatingScript ] = useState < { id : number ; containerId : string ; server ?: any } | null > ( null ) ;
30
32
const [ editingScriptId , setEditingScriptId ] = useState < number | null > ( null ) ;
31
33
const [ editFormData , setEditFormData ] = useState < { script_name : string ; container_id : string } > ( { script_name : '' , container_id : '' } ) ;
@@ -154,20 +156,58 @@ export function InstalledScriptsTab() {
154
156
}
155
157
} , [ scripts . length , serversData ?. servers , cleanupMutation ] ) ;
156
158
157
- // Filter scripts based on search and filters
158
- const filteredScripts = scripts . filter ( ( script : InstalledScript ) => {
159
- const matchesSearch = script . script_name . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
160
- ( script . container_id ?. includes ( searchTerm ) ?? false ) ||
161
- ( script . server_name ?. toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ?? false ) ;
162
-
163
- const matchesStatus = statusFilter === 'all' || script . status === statusFilter ;
164
-
165
- const matchesServer = serverFilter === 'all' ||
166
- ( serverFilter === 'local' && ! script . server_name ) ||
167
- ( script . server_name === serverFilter ) ;
168
-
169
- return matchesSearch && matchesStatus && matchesServer ;
170
- } ) ;
159
+ // Filter and sort scripts
160
+ const filteredScripts = scripts
161
+ . filter ( ( script : InstalledScript ) => {
162
+ const matchesSearch = script . script_name . toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ||
163
+ ( script . container_id ?. includes ( searchTerm ) ?? false ) ||
164
+ ( script . server_name ?. toLowerCase ( ) . includes ( searchTerm . toLowerCase ( ) ) ?? false ) ;
165
+
166
+ const matchesStatus = statusFilter === 'all' || script . status === statusFilter ;
167
+
168
+ const matchesServer = serverFilter === 'all' ||
169
+ ( serverFilter === 'local' && ! script . server_name ) ||
170
+ ( script . server_name === serverFilter ) ;
171
+
172
+ return matchesSearch && matchesStatus && matchesServer ;
173
+ } )
174
+ . sort ( ( a : InstalledScript , b : InstalledScript ) => {
175
+ let aValue : any ;
176
+ let bValue : any ;
177
+
178
+ switch ( sortField ) {
179
+ case 'script_name' :
180
+ aValue = a . script_name . toLowerCase ( ) ;
181
+ bValue = b . script_name . toLowerCase ( ) ;
182
+ break ;
183
+ case 'container_id' :
184
+ aValue = a . container_id ?? '' ;
185
+ bValue = b . container_id ?? '' ;
186
+ break ;
187
+ case 'server_name' :
188
+ aValue = a . server_name ?? 'Local' ;
189
+ bValue = b . server_name ?? 'Local' ;
190
+ break ;
191
+ case 'status' :
192
+ aValue = a . status ;
193
+ bValue = b . status ;
194
+ break ;
195
+ case 'installation_date' :
196
+ aValue = new Date ( a . installation_date ) . getTime ( ) ;
197
+ bValue = new Date ( b . installation_date ) . getTime ( ) ;
198
+ break ;
199
+ default :
200
+ return 0 ;
201
+ }
202
+
203
+ if ( aValue < bValue ) {
204
+ return sortDirection === 'asc' ? - 1 : 1 ;
205
+ }
206
+ if ( aValue > bValue ) {
207
+ return sortDirection === 'asc' ? 1 : - 1 ;
208
+ }
209
+ return 0 ;
210
+ } ) ;
171
211
172
212
// Get unique servers for filter
173
213
const uniqueServers : string [ ] = [ ] ;
@@ -298,6 +338,15 @@ export function InstalledScriptsTab() {
298
338
setAutoDetectServerId ( '' ) ;
299
339
} ;
300
340
341
+ const handleSort = ( field : 'script_name' | 'container_id' | 'server_name' | 'status' | 'installation_date' ) => {
342
+ if ( sortField === field ) {
343
+ setSortDirection ( sortDirection === 'asc' ? 'desc' : 'asc' ) ;
344
+ } else {
345
+ setSortField ( field ) ;
346
+ setSortDirection ( 'asc' ) ;
347
+ }
348
+ } ;
349
+
301
350
302
351
const formatDate = ( dateString : string ) => {
303
352
return new Date ( dateString ) . toLocaleString ( ) ;
@@ -652,20 +701,70 @@ export function InstalledScriptsTab() {
652
701
< table className = "min-w-full divide-y divide-gray-200" >
653
702
< thead className = "bg-muted" >
654
703
< tr >
655
- < th className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider" >
656
- Script Name
704
+ < th
705
+ className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
706
+ onClick = { ( ) => handleSort ( 'script_name' ) }
707
+ >
708
+ < div className = "flex items-center space-x-1" >
709
+ < span > Script Name</ span >
710
+ { sortField === 'script_name' && (
711
+ < span className = "text-primary" >
712
+ { sortDirection === 'asc' ? '↑' : '↓' }
713
+ </ span >
714
+ ) }
715
+ </ div >
657
716
</ th >
658
- < th className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider" >
659
- Container ID
717
+ < th
718
+ className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
719
+ onClick = { ( ) => handleSort ( 'container_id' ) }
720
+ >
721
+ < div className = "flex items-center space-x-1" >
722
+ < span > Container ID</ span >
723
+ { sortField === 'container_id' && (
724
+ < span className = "text-primary" >
725
+ { sortDirection === 'asc' ? '↑' : '↓' }
726
+ </ span >
727
+ ) }
728
+ </ div >
660
729
</ th >
661
- < th className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider" >
662
- Server
730
+ < th
731
+ className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
732
+ onClick = { ( ) => handleSort ( 'server_name' ) }
733
+ >
734
+ < div className = "flex items-center space-x-1" >
735
+ < span > Server</ span >
736
+ { sortField === 'server_name' && (
737
+ < span className = "text-primary" >
738
+ { sortDirection === 'asc' ? '↑' : '↓' }
739
+ </ span >
740
+ ) }
741
+ </ div >
663
742
</ th >
664
- < th className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider" >
665
- Status
743
+ < th
744
+ className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
745
+ onClick = { ( ) => handleSort ( 'status' ) }
746
+ >
747
+ < div className = "flex items-center space-x-1" >
748
+ < span > Status</ span >
749
+ { sortField === 'status' && (
750
+ < span className = "text-primary" >
751
+ { sortDirection === 'asc' ? '↑' : '↓' }
752
+ </ span >
753
+ ) }
754
+ </ div >
666
755
</ th >
667
- < th className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider" >
668
- Installation Date
756
+ < th
757
+ className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider cursor-pointer hover:bg-muted/80 select-none"
758
+ onClick = { ( ) => handleSort ( 'installation_date' ) }
759
+ >
760
+ < div className = "flex items-center space-x-1" >
761
+ < span > Installation Date</ span >
762
+ { sortField === 'installation_date' && (
763
+ < span className = "text-primary" >
764
+ { sortDirection === 'asc' ? '↑' : '↓' }
765
+ </ span >
766
+ ) }
767
+ </ div >
669
768
</ th >
670
769
< th className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider" >
671
770
Actions
0 commit comments