diff --git a/app/client/modules/volumes/routes/volumes.tsx b/app/client/modules/volumes/routes/volumes.tsx index 5589dfd7..26ab105a 100644 --- a/app/client/modules/volumes/routes/volumes.tsx +++ b/app/client/modules/volumes/routes/volumes.tsx @@ -1,5 +1,5 @@ import { useSuspenseQuery } from "@tanstack/react-query"; -import { HardDrive, Plus, RotateCcw } from "lucide-react"; +import { ArrowDown, ArrowUp, ArrowUpDown, HardDrive, Plus, RotateCcw } from "lucide-react"; import { useState } from "react"; import { EmptyState } from "~/client/components/empty-state"; import { StatusDot } from "~/client/components/status-dot"; @@ -24,10 +24,32 @@ const getVolumeStatusVariant = (status: VolumeStatus): "success" | "neutral" | " return statusMap[status]; }; +type SortColumn = "name" | "backend" | "status"; +type SortDirection = "asc" | "desc"; +type VolumeRow = { + shortId: string; + name: string; + type: "directory" | "nfs" | "smb" | "webdav" | "sftp" | "rclone"; + status: VolumeStatus; +}; + +const getSortValue = (column: SortColumn, volume: VolumeRow) => { + switch (column) { + case "name": + return volume.name; + case "backend": + return volume.type; + case "status": + return volume.status; + } +}; + export function VolumesPage() { const [searchQuery, setSearchQuery] = useState(""); const [statusFilter, setStatusFilter] = useState(""); const [backendFilter, setBackendFilter] = useState(""); + const [sortColumn, setSortColumn] = useState("name"); + const [sortDirection, setSortDirection] = useState("asc"); const clearFilters = () => { setSearchQuery(""); @@ -41,16 +63,48 @@ export function VolumesPage() { ...listVolumesOptions(), }); + const volumes = data as VolumeRow[]; + + const toggleSort = (column: SortColumn) => { + if (sortColumn === column) { + setSortDirection((prev) => (prev === "asc" ? "desc" : "asc")); + return; + } + + setSortColumn(column); + setSortDirection("asc"); + }; + + const renderSortIcon = (column: SortColumn) => { + if (sortColumn !== column) { + return ; + } + + return sortDirection === "asc" ? ( + + ) : ( + + ); + }; + const filteredVolumes = - data.filter((volume) => { + volumes.filter((volume) => { const matchesSearch = volume.name.toLowerCase().includes(searchQuery.toLowerCase()); const matchesStatus = !statusFilter || volume.status === statusFilter; const matchesBackend = !backendFilter || volume.type === backendFilter; return matchesSearch && matchesStatus && matchesBackend; }) || []; - const hasNoVolumes = data.length === 0; - const hasNoFilteredVolumes = filteredVolumes.length === 0 && !hasNoVolumes; + const sortedFilteredVolumes = [...filteredVolumes].sort((a, b) => { + const valueA = getSortValue(sortColumn, a).toLowerCase(); + const valueB = getSortValue(sortColumn, b).toLowerCase(); + const result = valueA.localeCompare(valueB); + + return sortDirection === "asc" ? result : -result; + }); + + const hasNoVolumes = volumes.length === 0; + const hasNoFilteredVolumes = sortedFilteredVolumes.length === 0 && !hasNoVolumes; if (hasNoVolumes) { return ( @@ -117,9 +171,43 @@ export function VolumesPage() { - Name - Backend - Status + + + + + + + + + @@ -134,7 +222,7 @@ export function VolumesPage() { - {filteredVolumes.map((volume) => ( + {sortedFilteredVolumes.map((volume) => ( - {filteredVolumes.length} volume - {filteredVolumes.length > 1 ? "s" : ""} + {sortedFilteredVolumes.length} volume + {sortedFilteredVolumes.length > 1 ? "s" : ""} )}