Add sorting to tables in LibraryManagementView

This commit is contained in:
grimsi
2025-06-14 14:28:27 +02:00
parent f2823760ce
commit c969bdeda3
2 changed files with 85 additions and 21 deletions
@@ -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<SortDescriptor>({column: "title", direction: "ascending"});
const [selectedGame, setSelectedGame] = useState<GameDto>(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
</Select>
</div>
<Table removeWrapper isStriped
sortDescriptor={sortDescriptor}
onSortChange={setSortDescriptor}
bottomContent={
<div className="flex w-full justify-center sticky">
{items.length > 0 &&
{pagedItems.length > 0 &&
<Pagination
isCompact
showControls
@@ -122,14 +155,14 @@ export default function LibraryManagementGames({library}: LibraryManagementGames
</div>
}>
<TableHeader>
<TableColumn allowsSorting>Game</TableColumn>
<TableColumn allowsSorting>Added to library</TableColumn>
<TableColumn allowsSorting>Download count</TableColumn>
<TableColumn key="title" allowsSorting>Game</TableColumn>
<TableColumn key="addedToLibrary" allowsSorting>Added to library</TableColumn>
<TableColumn key="downloadCount" allowsSorting>Download count</TableColumn>
<TableColumn>Path</TableColumn>
{/* width={1} keeps the column as far to the right as possible*/}
<TableColumn width={1}>Actions</TableColumn>
</TableHeader>
<TableBody emptyContent="Your filter did not match any games." items={items}>
<TableBody emptyContent="Your filter did not match any games." items={pagedItems}>
{(item) => (
<TableRow key={item.id}>
<TableCell>
@@ -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<SortDescriptor>({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 <div className="flex flex-col gap-4">
<h1 className="text-2xl font-bold">Manage unmatched paths</h1>
<Input
@@ -62,9 +91,11 @@ export default function LibraryManagementUnmatchedPaths({library}: LibraryManage
onClear={() => setSearchTerm("")}
/>
<Table removeWrapper isStriped isHeaderSticky
sortDescriptor={sortDescriptor}
onSortChange={setSortDescriptor}
bottomContent={
<div className="flex w-full justify-center">
{items.length > 0 &&
{pagedPaths.length > 0 &&
<Pagination
isCompact
showControls
@@ -77,10 +108,10 @@ export default function LibraryManagementUnmatchedPaths({library}: LibraryManage
</div>
}>
<TableHeader>
<TableColumn allowsSorting>Path</TableColumn>
<TableColumn key="path" allowsSorting>Path</TableColumn>
<TableColumn width={1}>Actions</TableColumn>
</TableHeader>
<TableBody emptyContent="This library has no unmatched paths." items={items}>
<TableBody emptyContent="This library has no unmatched paths." items={pagedPaths}>
{(item) => (
<TableRow key={item.key}>
<TableCell>