@@ -26,6 +26,8 @@ export function InstalledScriptsTab() {
2626 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
2727 const [ statusFilter , setStatusFilter ] = useState < 'all' | 'success' | 'failed' | 'in_progress' > ( 'all' ) ;
2828 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' ) ;
2931 const [ updatingScript , setUpdatingScript ] = useState < { id : number ; containerId : string ; server ?: any } | null > ( null ) ;
3032 const [ editingScriptId , setEditingScriptId ] = useState < number | null > ( null ) ;
3133 const [ editFormData , setEditFormData ] = useState < { script_name : string ; container_id : string } > ( { script_name : '' , container_id : '' } ) ;
@@ -154,20 +156,58 @@ export function InstalledScriptsTab() {
154156 }
155157 } , [ scripts . length , serversData ?. servers , cleanupMutation ] ) ;
156158
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+ } ) ;
171211
172212 // Get unique servers for filter
173213 const uniqueServers : string [ ] = [ ] ;
@@ -298,6 +338,15 @@ export function InstalledScriptsTab() {
298338 setAutoDetectServerId ( '' ) ;
299339 } ;
300340
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+
301350
302351 const formatDate = ( dateString : string ) => {
303352 return new Date ( dateString ) . toLocaleString ( ) ;
@@ -652,20 +701,70 @@ export function InstalledScriptsTab() {
652701 < table className = "min-w-full divide-y divide-gray-200" >
653702 < thead className = "bg-muted" >
654703 < 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 >
657716 </ 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 >
660729 </ 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 >
663742 </ 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 >
666755 </ 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 >
669768 </ th >
670769 < th className = "px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider" >
671770 Actions
0 commit comments