Skip to content

Commit

Permalink
refactor(src): 페이지네이션 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
vi-wolhwa committed Aug 6, 2024
1 parent 916f8f6 commit 6d8f90d
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 8 deletions.
10 changes: 9 additions & 1 deletion frontend/src/api/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ const API_URL = process.env.REACT_APP_API_URL;

export const TEMPLATE_API_URL = `${API_URL}/templates`;

export const getTemplateList = async (categoryId?: number, tagId?: number): Promise<TemplateListResponse> => {
export const getTemplateList = async (
categoryId?: number,
tagId?: number,
page: number = 1,
pageSize: number = 20,
): Promise<TemplateListResponse> => {
const url = new URL(TEMPLATE_API_URL);

if (categoryId) {
Expand All @@ -16,6 +21,9 @@ export const getTemplateList = async (categoryId?: number, tagId?: number): Prom
url.searchParams.append('tag', tagId.toString());
}

url.searchParams.append('page', page.toString());
url.searchParams.append('pageSize', pageSize.toString());

return await customFetch({
url: url.toString(),
});
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/components/PagingButton/PagingButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Meta, StoryObj } from '@storybook/react';

import PagingButton from './PagingButton';

const meta: Meta<typeof PagingButton> = {
title: 'PagingButton',
component: PagingButton,
};

export default meta;

type Story = StoryObj<typeof PagingButton>;

export const Default: Story = {
render: () => (
<div>
<PagingButton page={1} isActive={false} onClick={() => {}} />,
<PagingButton page={2} isActive={false} onClick={() => {}} />,
<PagingButton page={3} isActive={false} onClick={() => {}} />,
</div>
),
};

export const Active: Story = {
render: () => (
<div>
<PagingButton page={1} isActive={true} onClick={() => {}} />,
<PagingButton page={2} isActive={true} onClick={() => {}} />,
<PagingButton page={3} isActive={true} onClick={() => {}} />,
</div>
),
};
28 changes: 28 additions & 0 deletions frontend/src/components/PagingButton/PagingButton.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import styled from '@emotion/styled';

import { theme } from '../../style/theme';

export const StyledPagingButton = styled.button`
cursor: pointer;
width: 1.5rem;
height: 1.5rem;
margin: 0 0.25rem;
color: ${theme.color.light.secondary_500};
background: none;
border: none;
:disabled {
background-color: ${theme.color.light.primary_800};
span {
color: ${theme.color.light.white};
}
}
:not(:disabled):hover span {
color: ${theme.color.light.secondary_700};
}
`;
17 changes: 17 additions & 0 deletions frontend/src/components/PagingButton/PagingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Text } from '@/components';
import { theme } from '@/style/theme';
import * as S from './PagingButton.style';

interface PagingButtonProps {
page: number;
isActive: boolean;
onClick: (page: number) => void;
}

const PagingButton = ({ page, isActive, onClick }: PagingButtonProps) => (
<S.StyledPagingButton disabled={isActive} onClick={() => onClick(page)}>
<Text.Medium color={theme.color.light.secondary_500}>{page}</Text.Medium>
</S.StyledPagingButton>
);

export default PagingButton;
1 change: 1 addition & 0 deletions frontend/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as Header } from './Header/Header';
export { default as Heading } from './Heading/Heading';
export { default as Input } from './Input/Input';
export { default as Layout } from './Layout/Layout';
export { default as PagingButton } from './PagingButton/PagingButton';
export { default as SelectList } from './SelectList/SelectList';
export { default as TagButton } from './TagButton/TagButton';
export { default as TemplateCard } from './TemplateCard/TemplateCard';
Expand Down
16 changes: 15 additions & 1 deletion frontend/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const handlers = [
const url = new URL(req.request.url);
const categoryId = url.searchParams.get('category');
const tagId = url.searchParams.get('tag');
const page = parseInt(url.searchParams.get('page') || '1', 10);
const pageSize = parseInt(url.searchParams.get('pageSize') || '20', 10);

let filteredTemplates = mockTemplateList.templates;

Expand All @@ -23,7 +25,19 @@ export const handlers = [
);
}

return HttpResponse.json({ templates: filteredTemplates });
const totalElements = filteredTemplates.length;
const totalPages = Math.ceil(totalElements / pageSize);
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedTemplates = filteredTemplates.slice(startIndex, endIndex);
const numberOfElements = paginatedTemplates.length;

return HttpResponse.json({
templates: paginatedTemplates,
totalPages,
totalElements,
numberOfElements,
});
}),
http.get(`${TEMPLATE_API_URL}/:id`, () => HttpResponse.json(mockTemplate)),
http.post(`${TEMPLATE_API_URL}`, async () => HttpResponse.json({ status: 201 })),
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/pages/MyTemplatesPage/MyTemplatePage.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
import { useState, useCallback } from 'react';

import { searchIcon } from '@/assets/images';
import { CategoryMenu, Flex, Heading, Input, TemplateGrid } from '@/components';
import { CategoryMenu, Flex, Heading, Input, TemplateGrid, PagingButton } from '@/components';
import { useWindowWidth } from '@/hooks';
import { useCategoryListQuery } from '@/queries/category';
import { useTemplateListQuery } from '@/queries/template';
import { theme } from '@/style/theme';
import { scroll } from '@/utils';
import * as S from './MyTemplatePage.style';

const getGridCols = (windowWidth: number) => (windowWidth <= 1024 ? 1 : 2);

const MyTemplatePage = () => {
const windowWidth = useWindowWidth();
const [selectedCategoryId, setSelectedCategoryId] = useState<number | undefined>(undefined);
const [page, setPage] = useState<number>(1);
const [pageSize] = useState<number>(20);

const { data: templateData } = useTemplateListQuery({
categoryId: selectedCategoryId,
page,
pageSize,
});
const { data: categoryData } = useCategoryListQuery();

const handleCategorySelect = useCallback((categoryId: number) => {
scroll.top();
setSelectedCategoryId(categoryId);
setPage(1);
}, []);

const templates = templateData?.templates || [];
const categories = categoryData?.categories || [];
const totalPages = templateData?.totalPages || 0;

const handlePageChange = (page: number) => {
scroll.top();
setPage(page);
};

return (
<S.MyTemplatePageContainer>
Expand All @@ -51,6 +64,11 @@ const MyTemplatePage = () => {
</S.SearchInput>
</Flex>
<TemplateGrid templates={templates} cols={getGridCols(windowWidth)} />
<Flex justify='center'>
{[...Array(totalPages)].map((_, index) => (
<PagingButton key={index + 1} page={index + 1} isActive={page === index + 1} onClick={handlePageChange} />
))}
</Flex>
</Flex>
</S.MainContainer>
</S.MyTemplatePageContainer>
Expand Down
13 changes: 8 additions & 5 deletions frontend/src/queries/template/useTemplateListQuery.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { useQuery } from '@tanstack/react-query';
import { keepPreviousData, useQuery } from '@tanstack/react-query';

import { QUERY_KEY, getTemplateList } from '@/api';
import type { TemplateListResponse } from '@/types';

interface UseTemplateListQueryProps {
interface Props {
categoryId?: number;
tagId?: number;
page?: number;
pageSize?: number;
}

export const useTemplateListQuery = ({ categoryId, tagId }: UseTemplateListQueryProps) =>
export const useTemplateListQuery = ({ categoryId, tagId, page = 1, pageSize = 20 }: Props) =>
useQuery<TemplateListResponse, Error>({
queryKey: [QUERY_KEY.TEMPLATE_LIST, categoryId, tagId],
queryFn: () => getTemplateList(categoryId, tagId),
queryKey: [QUERY_KEY.TEMPLATE_LIST, categoryId, tagId, page, pageSize],
queryFn: () => getTemplateList(categoryId, tagId, page, pageSize),
placeholderData: keepPreviousData,
});
1 change: 1 addition & 0 deletions frontend/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface TemplateEditRequest {

export interface TemplateListResponse {
templates: Template[];
totalPages: number;
}

export interface TemplateUploadRequest {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { formatRelativeTime } from './formatRelativeTime';
export { getLanguageByFilename } from './getLanguageByFileName';
export { scroll } from './scroll';
10 changes: 10 additions & 0 deletions frontend/src/utils/scroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const top = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
};

export const scroll = {
top,
};

0 comments on commit 6d8f90d

Please sign in to comment.