Skip to content

Commit c5a2916

Browse files
committed
Fix
1 parent ce0dcab commit c5a2916

1 file changed

Lines changed: 43 additions & 15 deletions

File tree

src/serving/songs/SongsPage.tsx

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { memo, useMemo, useCallback } from "react";
22
import { ApiHelper, Loading, Locale, PageHeader, UserHelper, Permissions } from "@churchapps/apphelper";
33
import { Link, Navigate } from "react-router-dom";
4-
import { Button, Box, Card, CardContent, Typography, Stack, Avatar, Chip, IconButton, TextField, InputAdornment, Tooltip, Checkbox } from "@mui/material";
4+
import { Button, Box, Card, CardContent, Typography, Stack, Avatar, Chip, IconButton, TextField, InputAdornment, Tooltip, Checkbox, TablePagination } from "@mui/material";
55
import { MusicNote as MusicIcon, LibraryMusic as LibraryIcon, Add as AddIcon, Search as SearchIcon, PlayCircle as PlayIcon, Timer as TimerIcon, Person as ArtistIcon, Delete as DeleteIcon } from "@mui/icons-material";
66
import { SongSearchDialog } from "./SongSearchDialog";
77
import { EmptyState } from "../../components/ui/EmptyState";
@@ -20,11 +20,24 @@ export const SongsPage = memo(() => {
2020
const [selected, setSelected] = React.useState<Set<string>>(new Set());
2121
const { confirm, ConfirmDialogElement } = useConfirmDelete();
2222

23-
const songs = useQuery<SongDetailInterface[]>({
24-
queryKey: ["/songDetails", "ContentApi"],
25-
placeholderData: []
23+
const [page, setPage] = React.useState(0);
24+
const [rowsPerPage, setRowsPerPage] = React.useState(10);
25+
26+
const songs = useQuery<{ songDetails: SongDetailInterface[], count: number }>({
27+
queryKey: [`/songDetails?limit=${rowsPerPage}&offset=${page * rowsPerPage}&search=${searchFilter}`, "ContentApi"],
28+
placeholderData: { songDetails: [], count: 0 }
2629
});
2730

31+
const handlePageChange = useCallback((_: unknown, newPage: number) => {
32+
setPage(newPage);
33+
}, []);
34+
35+
const handleRowsPerPageChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
36+
const newLimit = parseInt(e.target.value, 10);
37+
setRowsPerPage(newLimit);
38+
setPage(0);
39+
}, []);
40+
2841
const handleAdd = useCallback(
2942
async (songDetail: SongDetailInterface) => {
3043
let selectedSong;
@@ -91,25 +104,26 @@ export const SongsPage = memo(() => {
91104
}, []);
92105

93106
const filteredSongs = useMemo(() => {
94-
if (!songs.data) return null;
107+
const songList = songs.data?.songDetails;
108+
if (!songList) return null;
95109
// Dedupe by songId: /songDetails join produces one row per arrangement.
96110
const seen = new Set<string>();
97-
const unique = songs.data.filter((song) => {
111+
const unique = songList.filter((song) => {
98112
const id = (song as any).songId || song.id;
99113
if (seen.has(id)) return false;
100114
seen.add(id);
101115
return true;
102116
});
103-
if (!searchFilter.trim()) return unique;
104-
105-
const filter = searchFilter.toLowerCase();
106-
return unique.filter((song) => song.title?.toLowerCase().includes(filter) || song.artist?.toLowerCase().includes(filter));
107-
}, [songs.data, searchFilter]);
117+
return unique;
118+
}, [songs.data?.songDetails]);
108119

109120
const songsContent = useMemo(() => {
110121
if (songs.isLoading) return <Loading size="sm" />;
111122

112-
if ((songs.data?.length ?? 0) === 0) {
123+
if ((songs.data?.songDetails?.length ?? 0) === 0) {
124+
if (searchFilter.trim()) {
125+
return <EmptyState icon={<SearchIcon />} title={Locale.label("songs.library.noResults") || "No songs match your search criteria."} />;
126+
}
113127
return (
114128
<EmptyState
115129
icon={<LibraryIcon />}
@@ -189,10 +203,21 @@ export const SongsPage = memo(() => {
189203
</Card>
190204
))}
191205
</Stack>
206+
<TablePagination
207+
component="div"
208+
count={songs.data?.count ?? 0}
209+
page={page}
210+
onPageChange={handlePageChange}
211+
rowsPerPage={rowsPerPage}
212+
onRowsPerPageChange={handleRowsPerPageChange}
213+
rowsPerPageOptions={[10, 25, 50]}
214+
sx={{ mt: 2 }}
215+
/>
192216
</Box>
193217
);
194218
}, [
195-
songs.isLoading, songs.data, filteredSongs, formatSeconds, handleImageError, failedImages, canEdit, selected, toggleSelected
219+
songs.isLoading, songs.data, filteredSongs, formatSeconds, handleImageError, failedImages, canEdit, selected, toggleSelected,
220+
page, rowsPerPage, handlePageChange, handleRowsPerPageChange
196221
]);
197222

198223
if (redirect) return <Navigate to={redirect} />;
@@ -221,7 +246,7 @@ export const SongsPage = memo(() => {
221246
</PageHeader>
222247

223248
<Box sx={{ p: 3 }}>
224-
{(showSearchField || searchFilter) && songs.data && songs.data.length > 0 && (
249+
{(showSearchField || searchFilter) && songs.data && ((songs.data.songDetails?.length ?? 0) > 0 || searchFilter) && (
225250
<Card sx={{ mb: 3, borderRadius: 2, border: "1px solid", borderColor: "divider" }}>
226251
<CardContent sx={{ pb: 2, "&:last-child": { pb: 2 } }}>
227252
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 2 }}>
@@ -235,7 +260,10 @@ export const SongsPage = memo(() => {
235260
variant="outlined"
236261
placeholder={Locale.label("songs.search.placeholder") || "Search songs by title or artist..."}
237262
value={searchFilter}
238-
onChange={(e) => setSearchFilter(e.target.value)}
263+
onChange={(e) => {
264+
setSearchFilter(e.target.value);
265+
setPage(0);
266+
}}
239267
InputProps={{
240268
startAdornment: (
241269
<InputAdornment position="start">

0 commit comments

Comments
 (0)