Skip to content

Commit 4ed3e42

Browse files
feat: Add sorting functionality to installed scripts table (#88)
* feat: Add sorting functionality to installed scripts table - Add sortable columns for Script Name, Container ID, Server, Status, and Installation Date - Implement clickable table headers with visual sort indicators - Add ascending/descending toggle functionality - Maintain existing search and filter functionality - Default sort by Script Name (ascending) - Handle null values gracefully in sorting logic * fix: Replace logical OR with nullish coalescing operator - Fix TypeScript ESLint errors in InstalledScriptsTab.tsx - Replace || with ?? for safer null/undefined handling - Build now passes successfully
1 parent a09f331 commit 4ed3e42

File tree

1 file changed

+123
-24
lines changed

1 file changed

+123
-24
lines changed

src/app/_components/InstalledScriptsTab.tsx

Lines changed: 123 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)