From c969bdeda3a0a0e63fbc3daddf935cea6676b6ef Mon Sep 17 00:00:00 2001 From: grimsi <9295182+grimsi@users.noreply.github.com> Date: Sat, 14 Jun 2025 14:28:27 +0200 Subject: [PATCH] Add sorting to tables in LibraryManagementView --- .../library/LibraryManagementGames.tsx | 51 ++++++++++++++--- .../LibraryManagementUnmatchedPaths.tsx | 55 +++++++++++++++---- 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/gameyfin/src/main/frontend/components/general/library/LibraryManagementGames.tsx b/gameyfin/src/main/frontend/components/general/library/LibraryManagementGames.tsx index 1b841bb..981a0d4 100644 --- a/gameyfin/src/main/frontend/components/general/library/LibraryManagementGames.tsx +++ b/gameyfin/src/main/frontend/components/general/library/LibraryManagementGames.tsx @@ -7,6 +7,7 @@ import { Pagination, Select, SelectItem, + SortDescriptor, Table, TableBody, TableCell, @@ -36,6 +37,7 @@ export default function LibraryManagementGames({library}: LibraryManagementGames const games = state.gamesByLibraryId[library.id] ? state.gamesByLibraryId[library.id] as GameDto[] : []; const [searchTerm, setSearchTerm] = useState(""); const [filter, setFilter] = useState<"all" | "confirmed" | "nonConfirmed">("all"); + const [sortDescriptor, setSortDescriptor] = useState({column: "title", direction: "ascending"}); const [selectedGame, setSelectedGame] = useState(games[0]); const editGameModal = useDisclosure(); @@ -46,12 +48,41 @@ export default function LibraryManagementGames({library}: LibraryManagementGames return Math.ceil(getFilteredGames().length / rowsPerPage); }, [games, filter]); - const items = useMemo(() => { + const filteredItems = useMemo(() => { + return getFilteredGames(); + }, [games, filter, searchTerm]); + + const sortedItems = useMemo(() => { + return filteredItems.slice().sort((a, b) => { + let cmp: number; + + switch (sortDescriptor.column) { + case "title": + cmp = a.title.localeCompare(b.title); + break; + case "addedToLibrary": + cmp = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); + break; + case "downloadCount": + cmp = a.metadata.downloadCount - b.metadata.downloadCount; + break; + default: + return 0; // No sorting if the column is not recognized + } + + if (sortDescriptor.direction === "descending") { + cmp *= -1; // Reverse the comparison if sorting in descending order + } + + return cmp; + }); + }, [filteredItems, sortDescriptor]); + + const pagedItems = useMemo(() => { const start = (page - 1) * rowsPerPage; const end = start + rowsPerPage; - - return getFilteredGames().slice(start, end); - }, [page, games, filter, searchTerm]); + return sortedItems.slice(start, end); + }, [page, sortedItems]); function getFilteredGames() { @@ -107,9 +138,11 @@ export default function LibraryManagementGames({library}: LibraryManagementGames - {items.length > 0 && + {pagedItems.length > 0 && }> - Game - Added to library - Download count + Game + Added to library + Download count Path {/* width={1} keeps the column as far to the right as possible*/} Actions - + {(item) => ( diff --git a/gameyfin/src/main/frontend/components/general/library/LibraryManagementUnmatchedPaths.tsx b/gameyfin/src/main/frontend/components/general/library/LibraryManagementUnmatchedPaths.tsx index aafc18c..e1afba5 100644 --- a/gameyfin/src/main/frontend/components/general/library/LibraryManagementUnmatchedPaths.tsx +++ b/gameyfin/src/main/frontend/components/general/library/LibraryManagementUnmatchedPaths.tsx @@ -3,6 +3,7 @@ import { Button, Input, Pagination, + SortDescriptor, Table, TableBody, TableCell, @@ -25,23 +26,45 @@ interface LibraryManagementUnmatchedPathsProps { export default function LibraryManagementUnmatchedPaths({library}: LibraryManagementUnmatchedPathsProps) { const matchGameModal = useDisclosure(); - const [searchTerm, setSearchTerm] = useState(""); const [page, setPage] = useState(1); const rowsPerPage = 25; - const filteredItems = useMemo(() => { + const [searchTerm, setSearchTerm] = useState(""); + const [selectedPath, setSelectedPath] = useState(library.unmatchedPaths ? library.unmatchedPaths[0] : null); + const [sortDescriptor, setSortDescriptor] = useState({column: "path", direction: "ascending"}); + + const pages = useMemo(() => { + return Math.ceil(getFilteredPaths().length / rowsPerPage); + }, [library.unmatchedPaths, searchTerm]); + + const filteredPaths = useMemo(() => { return library.unmatchedPaths! .filter((path) => path.toLowerCase().includes(searchTerm.toLowerCase())) .map((path) => ({key: hashCode(path), path})); - }, [searchTerm, library]); + }, [library, searchTerm]); - const pages = useMemo(() => Math.ceil(filteredItems.length / rowsPerPage), [filteredItems]); - const items = useMemo(() => { + const sortedPaths = useMemo(() => { + return filteredPaths.slice().sort((a, b) => { + let cmp: number; + switch (sortDescriptor.column) { + case "path": + cmp = a.path.localeCompare(b.path); + break; + default: + cmp = 0; + } + if (sortDescriptor.direction === "descending") { + cmp *= -1; + } + return cmp; + }); + }, [filteredPaths, sortDescriptor]); + + const pagedPaths = useMemo(() => { const start = (page - 1) * rowsPerPage; - return filteredItems.slice(start, start + rowsPerPage); - }, [page, filteredItems]); - - const [selectedPath, setSelectedPath] = useState(library.unmatchedPaths ? library.unmatchedPaths[0] : null); + const end = start + rowsPerPage; + return sortedPaths.slice(start, end); + }, [page, sortedPaths]); async function deleteUnmatchedPath(unmatchedPath: string) { const libraryUpdateDto: LibraryUpdateDto = { @@ -51,6 +74,12 @@ export default function LibraryManagementUnmatchedPaths({library}: LibraryManage await LibraryEndpoint.updateLibrary(libraryUpdateDto); } + function getFilteredPaths() { + return library.unmatchedPaths!!.filter((path) => + path.toLowerCase().includes(searchTerm.toLowerCase()) + ) + } + return

Manage unmatched paths

setSearchTerm("")} />
- {items.length > 0 && + {pagedPaths.length > 0 && }> - Path + Path Actions - + {(item) => (