From 7bcc5ec8f2babfdf4b2688ff8669485b68454e29 Mon Sep 17 00:00:00 2001 From: maxidragon Date: Wed, 20 Sep 2023 17:03:29 +0200 Subject: [PATCH] Add pagination for quizzes --- backend/app/controllers/quizzes_controller.rb | 5 +- .../Pagination/PaginationFooter.tsx | 66 +++++++++++++++++++ frontend/src/Layout/Layout.tsx | 2 +- frontend/src/Layout/Navbar.tsx | 2 +- frontend/src/Pages/Home/Home.tsx | 44 ++++++++++--- frontend/src/logic/auth.ts | 2 +- frontend/src/logic/interfaces.ts | 37 +++++------ frontend/src/logic/quizzes.ts | 4 +- 8 files changed, 127 insertions(+), 35 deletions(-) create mode 100644 frontend/src/Components/Pagination/PaginationFooter.tsx diff --git a/backend/app/controllers/quizzes_controller.rb b/backend/app/controllers/quizzes_controller.rb index ab488e0..84bd475 100644 --- a/backend/app/controllers/quizzes_controller.rb +++ b/backend/app/controllers/quizzes_controller.rb @@ -4,7 +4,7 @@ class QuizzesController < ApplicationController def index page = params[:page] || 1 - per_page = params[:per_page] || 10 # Liczba wyników na stronę (możesz dostosować) + per_page = params[:per_page] || 10 if params[:search] && params[:search] != "" quizzes = Quiz.where("name LIKE ? OR description LIKE ?", "%#{params[:search]}%", "%#{params[:search]}%") @@ -18,7 +18,8 @@ def index render json: { quizzes: quizzes, current_page: page.to_i, - total_pages: total_pages + total_pages: total_pages, + total_items: quizzes.total_entries } end diff --git a/frontend/src/Components/Pagination/PaginationFooter.tsx b/frontend/src/Components/Pagination/PaginationFooter.tsx new file mode 100644 index 0000000..10dbd82 --- /dev/null +++ b/frontend/src/Components/Pagination/PaginationFooter.tsx @@ -0,0 +1,66 @@ +import { Typography, IconButton, Box } from "@mui/material"; +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; +import NavigateBeforeIcon from "@mui/icons-material/NavigateBefore"; +import { useEffect, useState } from "react"; + +const PaginationFooter = (props: { + page: number; + totalPages: number; + totalItems: number; + handlePageChange: (page: number) => void; +}) => { + const [isPreviousPageDisabled, setIsPreviousPageDisabled] = + useState(false); + const [isNextPageDisabled, setIsNextPageDisabled] = useState(false); + + useEffect(() => { + setIsPreviousPageDisabled(props.page === 1); + setIsNextPageDisabled(props.page === props.totalPages); + }, [props.page, props.totalPages]); + + const nextPage = () => { + if (props.page < props.totalPages) { + props.handlePageChange(props.page + 1); + } + }; + const previousPage = () => { + if (props.page > 1) { + props.handlePageChange(props.page - 1); + } + }; + + return ( + + + + + + Page {props.page} of {props.totalPages} + + + + + Total quizzes: {props.totalItems} + + ); +}; + +export default PaginationFooter; diff --git a/frontend/src/Layout/Layout.tsx b/frontend/src/Layout/Layout.tsx index a6ef12c..3d2d994 100644 --- a/frontend/src/Layout/Layout.tsx +++ b/frontend/src/Layout/Layout.tsx @@ -11,4 +11,4 @@ const Layout = (props: { children: React.ReactNode }) => { ); }; -export default Layout; \ No newline at end of file +export default Layout; diff --git a/frontend/src/Layout/Navbar.tsx b/frontend/src/Layout/Navbar.tsx index e89b6a8..92b2d0b 100644 --- a/frontend/src/Layout/Navbar.tsx +++ b/frontend/src/Layout/Navbar.tsx @@ -22,4 +22,4 @@ const Navbar = () => { ); }; -export default Navbar; \ No newline at end of file +export default Navbar; diff --git a/frontend/src/Pages/Home/Home.tsx b/frontend/src/Pages/Home/Home.tsx index 798aba9..fe1d21f 100644 --- a/frontend/src/Pages/Home/Home.tsx +++ b/frontend/src/Pages/Home/Home.tsx @@ -1,27 +1,47 @@ -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { Box, CircularProgress, TextField, Typography } from "@mui/material"; import { Quiz } from "../../logic/interfaces"; import { getQuizzes } from "../../logic/quizzes"; import QuizCard from "../../Components/CardComponents/QuizCard"; +import PaginationFooter from "../../Components/Pagination/PaginationFooter"; const Home = () => { + const [perPage, setPerPage] = useState(10); const [quizzes, setQuizzes] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [totalItems, setTotalItems] = useState(0); - const fetchQuizzes = async (searchParam?: string) => { - const response = await getQuizzes(searchParam); - setQuizzes(response); - setLoading(false); - }; + const fetchQuizzes = useCallback( + async (searchParam?: string, pageParam: number = 1, perPageParam: number = 10) => { + const response = await getQuizzes(searchParam, pageParam, perPageParam); + setQuizzes(response.quizzes); + setTotalPages(response.total_pages); + setTotalItems(response.total_items); + setLoading(false); + }, + [] + ); const handleSearch = async (event: React.ChangeEvent) => { setSearch(event.target.value); - fetchQuizzes(event.target.value); + fetchQuizzes(event.target.value, 1, perPage); + }; + + const handlePageChange = async (pageParam: number) => { + setPage(pageParam); + const response = await getQuizzes(search, pageParam, perPage); + setQuizzes(response); }; useEffect(() => { - fetchQuizzes(); - }, []); + setTotalPages(1); + setTotalItems(0); + setPerPage(10); //remove it + setPage(1); + fetchQuizzes("", 1); + }, [fetchQuizzes]); return ( <> @@ -60,6 +80,12 @@ const Home = () => { ))} )} + ); diff --git a/frontend/src/logic/auth.ts b/frontend/src/logic/auth.ts index c098948..e34b154 100644 --- a/frontend/src/logic/auth.ts +++ b/frontend/src/logic/auth.ts @@ -51,4 +51,4 @@ export const logout = async () => { export const isUserLoggedIn = () => { return localStorage.getItem("token") !== null; -}; \ No newline at end of file +}; diff --git a/frontend/src/logic/interfaces.ts b/frontend/src/logic/interfaces.ts index a132959..eff2f26 100644 --- a/frontend/src/logic/interfaces.ts +++ b/frontend/src/logic/interfaces.ts @@ -1,32 +1,31 @@ export interface Quiz { - id: number; - name: string; - description: string; - questions: Question[]; + id: number; + name: string; + description: string; + questions: Question[]; } export interface Question { - id: number; - question: string; - answers: Answer[]; + id: number; + question: string; + answers: Answer[]; } - export interface Answer { - id: number; - answer: string; - isCorrect: boolean; + id: number; + answer: string; + isCorrect: boolean; } export interface User { - id: number; - name: string; - email: string; - password: string; + id: number; + name: string; + email: string; + password: string; } export interface UserAnswer { - id: number; - questionId: number; - answerId: number; -} \ No newline at end of file + id: number; + questionId: number; + answerId: number; +} diff --git a/frontend/src/logic/quizzes.ts b/frontend/src/logic/quizzes.ts index 037813c..711064f 100644 --- a/frontend/src/logic/quizzes.ts +++ b/frontend/src/logic/quizzes.ts @@ -1,10 +1,10 @@ import { backendRequest } from "./request"; -export const getQuizzes = async (searchParam?: string) => { +export const getQuizzes = async (searchParam?: string, pageParam: number = 1, perPage: number = 10) => { try { let url = "quizzes"; if (searchParam) { - url += `?search=${searchParam}`; + url += `?search=${searchParam}&page=${pageParam}&per_page=${perPage}`; } const response = await backendRequest(url, "GET", false); return await response.json();