feat: Add pagination logic and controls for videos

This commit is contained in:
Peifan Li
2025-11-22 20:12:02 -05:00
parent d97bbde963
commit 0e2a0a791d
3 changed files with 138 additions and 15 deletions

View File

@@ -7,6 +7,7 @@ import {
CircularProgress, CircularProgress,
Container, Container,
Grid, Grid,
Pagination,
Typography Typography
} from '@mui/material'; } from '@mui/material';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -29,6 +30,8 @@ const CollectionPage: React.FC<CollectionPageProps> = ({ collections, videos, on
const [collectionVideos, setCollectionVideos] = useState<Video[]>([]); const [collectionVideos, setCollectionVideos] = useState<Video[]>([]);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false); const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
const [page, setPage] = useState(1);
const ITEMS_PER_PAGE = 12;
useEffect(() => { useEffect(() => {
if (collections && collections.length > 0 && id) { if (collections && collections.length > 0 && id) {
@@ -50,8 +53,21 @@ const CollectionPage: React.FC<CollectionPageProps> = ({ collections, videos, on
} }
setLoading(false); setLoading(false);
setLoading(false);
}, [id, collections, videos, navigate]); }, [id, collections, videos, navigate]);
// Pagination logic
const totalPages = Math.ceil(collectionVideos.length / ITEMS_PER_PAGE);
const displayedVideos = collectionVideos.slice(
(page - 1) * ITEMS_PER_PAGE,
page * ITEMS_PER_PAGE
);
const handlePageChange = (event: React.ChangeEvent<unknown>, value: number) => {
setPage(value);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const handleBack = () => { const handleBack = () => {
navigate(-1); navigate(-1);
}; };
@@ -123,8 +139,9 @@ const CollectionPage: React.FC<CollectionPageProps> = ({ collections, videos, on
{collectionVideos.length === 0 ? ( {collectionVideos.length === 0 ? (
<Alert severity="info" variant="outlined">No videos in this collection.</Alert> <Alert severity="info" variant="outlined">No videos in this collection.</Alert>
) : ( ) : (
<Box>
<Grid container spacing={3}> <Grid container spacing={3}>
{collectionVideos.map(video => ( {displayedVideos.map(video => (
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }} key={video.id}> <Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }} key={video.id}>
<VideoCard <VideoCard
video={video} video={video}
@@ -136,6 +153,21 @@ const CollectionPage: React.FC<CollectionPageProps> = ({ collections, videos, on
</Grid> </Grid>
))} ))}
</Grid> </Grid>
{totalPages > 1 && (
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}>
<Pagination
count={totalPages}
page={page}
onChange={handlePageChange}
color="primary"
size="large"
showFirstButton
showLastButton
/>
</Box>
)}
</Box>
)} )}
<DeleteCollectionModal <DeleteCollectionModal

View File

@@ -11,8 +11,10 @@ import {
CircularProgress, CircularProgress,
Container, Container,
Grid, Grid,
Pagination,
Typography Typography
} from '@mui/material'; } from '@mui/material';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import AuthorsList from '../components/AuthorsList'; import AuthorsList from '../components/AuthorsList';
import Collections from '../components/Collections'; import Collections from '../components/Collections';
@@ -59,6 +61,13 @@ const Home: React.FC<HomeProps> = ({
onDownload, onDownload,
onResetSearch onResetSearch
}) => { }) => {
const [page, setPage] = useState(1);
const ITEMS_PER_PAGE = 12;
// Reset page when filters change (though currently no filters other than search which is separate)
useEffect(() => {
setPage(1);
}, [videos, collections]);
// Add default empty array to ensure videos is always an array // Add default empty array to ensure videos is always an array
@@ -101,6 +110,20 @@ const Home: React.FC<HomeProps> = ({
}); });
}); });
// Pagination logic
const totalPages = Math.ceil(filteredVideos.length / ITEMS_PER_PAGE);
const displayedVideos = filteredVideos.slice(
(page - 1) * ITEMS_PER_PAGE,
page * ITEMS_PER_PAGE
);
const handlePageChange = (event: React.ChangeEvent<unknown>, value: number) => {
setPage(value);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
// Helper function to format duration in seconds to MM:SS // Helper function to format duration in seconds to MM:SS
const formatDuration = (seconds?: number) => { const formatDuration = (seconds?: number) => {
if (!seconds) return ''; if (!seconds) return '';
@@ -270,7 +293,7 @@ const Home: React.FC<HomeProps> = ({
{/* Videos grid */} {/* Videos grid */}
<Grid size={{ xs: 12, md: 9 }}> <Grid size={{ xs: 12, md: 9 }}>
<Grid container spacing={3}> <Grid container spacing={3}>
{filteredVideos.map(video => ( {displayedVideos.map(video => (
<Grid size={{ xs: 12, sm: 6, lg: 4, xl: 3 }} key={video.id}> <Grid size={{ xs: 12, sm: 6, lg: 4, xl: 3 }} key={video.id}>
<VideoCard <VideoCard
video={video} video={video}
@@ -279,6 +302,20 @@ const Home: React.FC<HomeProps> = ({
</Grid> </Grid>
))} ))}
</Grid> </Grid>
{totalPages > 1 && (
<Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}>
<Pagination
count={totalPages}
page={page}
onChange={handlePageChange}
color="primary"
size="large"
showFirstButton
showLastButton
/>
</Box>
)}
</Grid> </Grid>
</Grid> </Grid>
)} )}

View File

@@ -13,6 +13,7 @@ import {
Container, Container,
IconButton, IconButton,
InputAdornment, InputAdornment,
Pagination,
Paper, Paper,
Table, Table,
TableBody, TableBody,
@@ -47,11 +48,38 @@ const ManagePage: React.FC<ManagePageProps> = ({ videos, onDeleteVideo, collecti
const [videoToDelete, setVideoToDelete] = useState<string | null>(null); const [videoToDelete, setVideoToDelete] = useState<string | null>(null);
const [showVideoDeleteModal, setShowVideoDeleteModal] = useState<boolean>(false); const [showVideoDeleteModal, setShowVideoDeleteModal] = useState<boolean>(false);
// Pagination state
const [collectionPage, setCollectionPage] = useState(1);
const [videoPage, setVideoPage] = useState(1);
const ITEMS_PER_PAGE = 10;
const filteredVideos = videos.filter(video => const filteredVideos = videos.filter(video =>
video.title.toLowerCase().includes(searchTerm.toLowerCase()) || video.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
video.author.toLowerCase().includes(searchTerm.toLowerCase()) video.author.toLowerCase().includes(searchTerm.toLowerCase())
); );
// Pagination logic
const totalCollectionPages = Math.ceil(collections.length / ITEMS_PER_PAGE);
const displayedCollections = collections.slice(
(collectionPage - 1) * ITEMS_PER_PAGE,
collectionPage * ITEMS_PER_PAGE
);
const totalVideoPages = Math.ceil(filteredVideos.length / ITEMS_PER_PAGE);
const displayedVideos = filteredVideos.slice(
(videoPage - 1) * ITEMS_PER_PAGE,
videoPage * ITEMS_PER_PAGE
);
const handleCollectionPageChange = (event: React.ChangeEvent<unknown>, value: number) => {
setCollectionPage(value);
};
const handleVideoPageChange = (event: React.ChangeEvent<unknown>, value: number) => {
setVideoPage(value);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
const confirmDeleteVideo = async () => { const confirmDeleteVideo = async () => {
if (!videoToDelete) return; if (!videoToDelete) return;
@@ -150,7 +178,7 @@ const ManagePage: React.FC<ManagePageProps> = ({ videos, onDeleteVideo, collecti
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{collections.map(collection => ( {displayedCollections.map(collection => (
<TableRow key={collection.id} hover> <TableRow key={collection.id} hover>
<TableCell component="th" scope="row" sx={{ fontWeight: 500 }}> <TableCell component="th" scope="row" sx={{ fontWeight: 500 }}>
{collection.name} {collection.name}
@@ -176,6 +204,19 @@ const ManagePage: React.FC<ManagePageProps> = ({ videos, onDeleteVideo, collecti
) : ( ) : (
<Alert severity="info" variant="outlined">No collections found.</Alert> <Alert severity="info" variant="outlined">No collections found.</Alert>
)} )}
{totalCollectionPages > 1 && (
<Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
<Pagination
count={totalCollectionPages}
page={collectionPage}
onChange={handleCollectionPageChange}
color="secondary"
showFirstButton
showLastButton
/>
</Box>
)}
</Box> </Box>
<Box> <Box>
@@ -212,7 +253,7 @@ const ManagePage: React.FC<ManagePageProps> = ({ videos, onDeleteVideo, collecti
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{filteredVideos.map(video => ( {displayedVideos.map(video => (
<TableRow key={video.id} hover> <TableRow key={video.id} hover>
<TableCell sx={{ width: 140 }}> <TableCell sx={{ width: 140 }}>
<Box <Box
@@ -246,6 +287,19 @@ const ManagePage: React.FC<ManagePageProps> = ({ videos, onDeleteVideo, collecti
<Alert severity="info" variant="outlined">No videos found matching your search.</Alert> <Alert severity="info" variant="outlined">No videos found matching your search.</Alert>
)} )}
</Box> </Box>
{totalVideoPages > 1 && (
<Box sx={{ mt: 2, mb: 4, display: 'flex', justifyContent: 'center' }}>
<Pagination
count={totalVideoPages}
page={videoPage}
onChange={handleVideoPageChange}
color="primary"
showFirstButton
showLastButton
/>
</Box>
)}
</Container> </Container>
); );
}; };