Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JOJI-702] 페이지네이션 #17

Merged
merged 13 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions src/app/_components/hooks/usePaigination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState } from "react";

const usePagination = (initialPage = 1, totalPages = 1, visiblePageCount=5) => {
const [currentPage, setCurrentPage] = useState(initialPage);
const [total, setTotalPages] = useState(totalPages);

const handlePageChange = (page) => {
if (page >= 1 && page <= total) {
setCurrentPage(page);
}
};

const goToNextPage = () => {
if (currentPage < total) {
setCurrentPage(currentPage + 1);
}
};

const goToPreviousPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
};

const getPageRange = () => {
const range = [];
const left = Math.max(1, currentPage - Math.floor(visiblePageCount / 2));
const right = Math.min(total, left + visiblePageCount - 1);

for (let i = left; i <= right; i++) {
range.push(i);
}

return range;
};

return {
currentPage,
totalPages: total,
setTotalPages,
handlePageChange,
goToNextPage,
goToPreviousPage,
getPageRange,
};
};

export default usePagination;
100 changes: 100 additions & 0 deletions src/app/_components/ui/pagination.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"

import { cn } from "@/lib/utils"
import { buttonVariants } from "@/app/_components/ui/button";

const Pagination = ({
className,
...props
}) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props} />
)
Pagination.displayName = "Pagination"

const PaginationContent = React.forwardRef(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn("flex flex-row items-center gap-1", className)}
{...props} />
))
PaginationContent.displayName = "PaginationContent"

const PaginationItem = React.forwardRef(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"

const PaginationLink = ({
className,
isActive,
size = "icon",
...props
}) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}), className)}
{...props} />
)
PaginationLink.displayName = "PaginationLink"

const PaginationPrevious = ({
className,
...props
}) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}>
<ChevronLeft className="h-4 w-4" />
<span>이전</span>
</PaginationLink>
)
PaginationPrevious.displayName = "PaginationPrevious"

const PaginationNext = ({
className,
...props
}) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}>
<span>다음</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
PaginationNext.displayName = "PaginationNext"

const PaginationEllipsis = ({
className,
...props
}) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
)
PaginationEllipsis.displayName = "PaginationEllipsis"

export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
}
92 changes: 65 additions & 27 deletions src/app/post/page.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,52 @@
"use client";

import { useEffect, useState } from "react";
import { Card, CardContent } from "@/app/_components/ui/card";
import { Skeleton } from "@/app/_components/ui/skeleton";
import { TypographyH2, TypographyP } from "@/app/_components/ui/typography";
import { Button } from "@/app/_components/ui/button";
import { useRouter } from "next/navigation";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
} from "@/app/_components/ui/pagination";
import usePagination from "../_components/hooks/usePaigination";

/**
* @typedef {Object} Post
* @property {number} id - 게시글 ID
* @property {string} title - 게시글 제목
* @property {string} author - 게시글 저자
* @property {string} category - 카테고리
* @property {string} createdAt - 작성일
* @property {number} views - 조회수
* @property {number} commentCount - 댓글 수
*/

export default function PostPage() {
export default function NoticePage() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const router = useRouter();

useEffect(() => {
const fetchPosts = async () => {
const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/posts`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
setPosts(json.data);
setLoading(false);
};
const {
currentPage,
totalPages,
setTotalPages,
handlePageChange,
goToNextPage,
goToPreviousPage,
getPageRange,
} = usePagination(1, 1); // 초기 페이지와 총 페이지 수 설정

const fetchPosts = async (page) => {
setLoading(true);
const response = await fetch(
`${process.env.NEXT_PUBLIC_BASE_URL}/posts?page=${page}&limit=6&categories=NOTICE`
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const notice = await response.json();
setPosts(notice.data);
setTotalPages(notice.meta.totalPages);
setLoading(false);
};

fetchPosts();
}, []);
useEffect(() => {
fetchPosts(currentPage);
}, [currentPage]);

if (loading) {
return (
Expand Down Expand Up @@ -82,7 +93,7 @@ export default function PostPage() {
</CardContent>
<div className="flex justify-end mt-4">
<Button
onClick={() => router.push(`/postDetail/${post.id}`)}
onClick={() => router.push(`/postDetail/${post.id}`)}
variant="outline"
className="text-orange-500 border-orange-500 hover:bg-orange-500 hover:text-white"
>
Expand All @@ -92,7 +103,34 @@ export default function PostPage() {
</Card>
))}
</div>

<Pagination className="mt-6">
<PaginationContent>
<PaginationPrevious
onClick={goToPreviousPage}
disabled={currentPage === 1}
/>
{getPageRange().map((page) => (
<PaginationItem key={page}>
<PaginationLink
isActive={currentPage === page}
onClick={() => handlePageChange(page)}
>
{page}
</PaginationLink>
</PaginationItem>
))}
{totalPages > currentPage + 3 && (
<PaginationItem>
<PaginationLink disabled>...</PaginationLink>
</PaginationItem>
)}
<PaginationNext
onClick={goToNextPage}
disabled={currentPage === totalPages}
/>
</PaginationContent>
</Pagination>
</div>
);
}

Loading