Skip to content

Commit

Permalink
Merge pull request #334 from vi-wolhwa/feat/my_template_sorting
Browse files Browse the repository at this point in the history
'내 템플릿' 페이지 정렬 기능
  • Loading branch information
Jaymyong66 authored Aug 7, 2024
2 parents 826471a + 180adaf commit ef4a7cd
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 30 deletions.
2 changes: 2 additions & 0 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export { QUERY_KEY } from './queryKeys';
export {
TEMPLATE_API_URL,
PAGE_SIZE,
SORTING_OPTIONS,
DEFAULT_SORTING_OPTION,
getTemplateList,
getTemplate,
postTemplate,
Expand Down
21 changes: 18 additions & 3 deletions frontend/src/api/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
TemplateUploadRequest,
TemplateListRequest,
} from '@/types';
import { SortingOption } from '@/types';
import { customFetch } from './customFetch';

const API_URL = process.env.REACT_APP_API_URL;
Expand All @@ -13,17 +14,29 @@ export const TEMPLATE_API_URL = `${API_URL}/templates`;

export const PAGE_SIZE = 20;

export const SORTING_OPTIONS: SortingOption[] = [
{
key: 'createdAt,desc',
value: '최근 생성 순',
},
{
key: 'createdAt,asc',
value: '오래된 생성 순',
},
];

export const DEFAULT_SORTING_OPTION = SORTING_OPTIONS[0];

export const getTemplateList = async ({
keyword = '',
categoryId,
tagIds,
sort = DEFAULT_SORTING_OPTION.key,
page = 1,
pageSize = PAGE_SIZE,
keyword = '',
}: TemplateListRequest): Promise<TemplateListResponse> => {
const url = new URL(TEMPLATE_API_URL);

url.searchParams.append('keyword', keyword);

if (categoryId) {
url.searchParams.append('categoryId', categoryId.toString());
}
Expand All @@ -32,6 +45,8 @@ export const getTemplateList = async ({
url.searchParams.append('tagIds', tagIds.toString());
}

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

Expand Down
12 changes: 8 additions & 4 deletions frontend/src/components/Dropdown/Dropdown.style.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import styled from '@emotion/styled';

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

export const DropdownContainer = styled.div`
font-size: '0.875rem';
`;

export const Wrapper = styled.div`
width: 10rem;
width: 9.125rem;
background-color: white;
border: 1px solid #788496;
border-radius: 8px;
Expand All @@ -17,16 +19,18 @@ export const SelectedButton = styled.button`
justify-content: space-between;
width: 100%;
height: 2.5rem;
padding: 0.75rem;
object-fit: contain;
border-radius: 8px;
`;

export const OptionList = styled.ul`
position: absolute;
z-index: 1;
width: 10rem;
width: 9.125rem;
margin: 2px 0 0 0;
background-color: white;
Expand All @@ -39,7 +43,7 @@ export const Option = styled.li`
padding: 0.75rem;
border-radius: 7px;
&:hover {
color: ${({ theme }) => theme.color.light.white};
background-color: ${({ theme }) => theme.color.light.primary_500};
color: ${theme.color.light.white};
background-color: ${theme.color.light.primary_500};
}
`;
10 changes: 6 additions & 4 deletions frontend/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { chevron } from '@/assets/images';
import { theme } from '../../style/theme';
import Text from '../Text/Text';
import * as S from './Dropdown.style';

interface Props<T> {
Expand Down Expand Up @@ -46,7 +48,7 @@ const Dropdown = <T,>({
<S.OptionList>
{options?.map((option, idx) => (
<S.Option key={idx} onClick={() => handleCurrentValue(option)}>
{getOptionLabel(option)}
<Text.Small color={theme.color.light.black}>{getOptionLabel(option)}</Text.Small>
</S.Option>
))}
</S.OptionList>
Expand All @@ -60,11 +62,11 @@ type SelectedButtonProps<T> = Pick<Props<T>, 'toggleDropdown' | 'getOptionLabel'

const SelectedButton = <T,>({ toggleDropdown, getOptionLabel, currentValue, isOpen }: SelectedButtonProps<T>) => (
<S.SelectedButton onClick={toggleDropdown}>
{getOptionLabel(currentValue)}
<Text.Small color={theme.color.light.black}>{getOptionLabel(currentValue)}</Text.Small>
<img
src={chevron}
width={24}
height={24}
width={16}
height={16}
alt=''
css={{
transition: 'transform 0.3s ease',
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const templateHandlers = [
const keyword = url.searchParams.get('keyword');
const categoryId = url.searchParams.get('categoryId');
const tagIds = url.searchParams.get('tagIds');
const sort = url.searchParams.get('sort');
const page = parseInt(url.searchParams.get('page') || '1', 10);
const pageSize = parseInt(url.searchParams.get('pageSize') || '20', 10);

Expand All @@ -44,6 +45,28 @@ export const templateHandlers = [
);
}

// API에서 createdAt가 추가되면 'createdAt'으로 변경한다.
switch (sort) {
case 'createdAt,asc':
filteredTemplates.sort((a, b) => new Date(a.modifiedAt).getTime() - new Date(b.modifiedAt).getTime());
break;

case 'createdAt,desc':
filteredTemplates.sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime());
break;

case 'modifiedAt,asc':
filteredTemplates.sort((a, b) => new Date(a.modifiedAt).getTime() - new Date(b.modifiedAt).getTime());
break;

case 'modifiedAt,desc':
filteredTemplates.sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime());
break;

default:
break;
}

const totalElements = filteredTemplates.length;
const totalPages = Math.ceil(totalElements / pageSize);
const startIndex = (page - 1) * pageSize;
Expand Down
35 changes: 23 additions & 12 deletions frontend/src/pages/MyTemplatesPage/MyTemplatePage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useState, useCallback } from 'react';

import { DEFAULT_SORTING_OPTION, SORTING_OPTIONS } from '@/api';
import { searchIcon } from '@/assets/images';
import { CategoryMenu, Flex, Heading, Input, TemplateGrid, PagingButton } from '@/components';
import { CategoryMenu, Flex, Heading, Input, TemplateGrid, PagingButton, Dropdown } from '@/components';
import { useWindowWidth, useDebounce } from '@/hooks';
import { useDropdown } from '@/hooks/utils/useDropdown';
import { useInput } from '@/hooks/utils/useInput';
import { useCategoryListQuery } from '@/queries/category';
import { useTemplateListQuery } from '@/queries/template';
Expand All @@ -14,18 +16,25 @@ 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 [keyword, handleKeywordChange] = useInput('');

const [keyword, handleKeywordChange] = useInput('');
const debouncedKeyword = useDebounce(keyword, 300);
const [selectedCategoryId, setSelectedCategoryId] = useState<number | undefined>(undefined);
const { currentValue: sortingOption, ...dropdownProps } = useDropdown(DEFAULT_SORTING_OPTION);

const [page, setPage] = useState<number>(1);

const { data: categoryData } = useCategoryListQuery();
const { data: templateData } = useTemplateListQuery({
keyword: debouncedKeyword,
categoryId: selectedCategoryId,
sort: sortingOption.key,
page,
keyword: debouncedKeyword,
});
const { data: categoryData } = useCategoryListQuery();

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

const handleCategorySelect = useCallback((categoryId: number) => {
scroll.top();
Expand All @@ -39,10 +48,6 @@ const MyTemplatePage = () => {
}
};

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

const handlePageChange = (page: number) => {
scroll.top();
setPage(page);
Expand All @@ -63,8 +68,8 @@ const MyTemplatePage = () => {
<CategoryMenu categories={categories} onSelectCategory={handleCategorySelect} />
</Flex>

<Flex direction='column' width='100%' gap='2rem'>
<Flex width='100%'>
<Flex direction='column' width='100%' gap='1rem'>
<Flex width='100%' gap='1rem'>
<S.SearchInput size='medium' variant='text'>
<Input.Adornment>
<img src={searchIcon} />
Expand All @@ -76,6 +81,12 @@ const MyTemplatePage = () => {
onKeyDown={handleSearchSubmit}
/>
</S.SearchInput>
<Dropdown
{...dropdownProps}
options={SORTING_OPTIONS}
currentValue={sortingOption}
getOptionLabel={(option) => option.value}
/>
</Flex>
<TemplateGrid templates={templates} cols={getGridCols(windowWidth)} />
<Flex justify='center'>
Expand Down
20 changes: 14 additions & 6 deletions frontend/src/queries/template/useTemplateListQuery.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { keepPreviousData, useQuery } from '@tanstack/react-query';

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

interface Props {
keyword?: string;
categoryId?: number;
tagIds?: number[];
sort?: SortingKey;
page?: number;
pageSize?: number;
keyword?: string;
}

export const useTemplateListQuery = ({ categoryId, tagIds, page = 1, pageSize = PAGE_SIZE, keyword }: Props) =>
export const useTemplateListQuery = ({
keyword,
categoryId,
tagIds,
sort = DEFAULT_SORTING_OPTION.key,
page = 1,
pageSize = PAGE_SIZE,
}: Props) =>
useQuery<TemplateListResponse, Error>({
queryKey: [QUERY_KEY.TEMPLATE_LIST, categoryId, tagIds, page, pageSize, keyword],
queryFn: () => getTemplateList({ categoryId, tagIds, page, pageSize, keyword }),
queryKey: [QUERY_KEY.TEMPLATE_LIST, keyword, categoryId, tagIds, sort, page, pageSize],
queryFn: () => getTemplateList({ keyword, categoryId, tagIds, sort, page, pageSize }),
placeholderData: keepPreviousData,
});
7 changes: 6 additions & 1 deletion frontend/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ export interface CategoryRequest {
}

export interface TemplateListRequest {
keyword?: string;
categoryId?: number;
tagIds?: number[];
sort?: SortingKey;
page?: number;
pageSize?: number;
keyword?: string;
}

export type SortingKey = 'createdAt,asc' | 'createdAt,desc';

export type SortingOption = { key: SortingKey; value: string };
2 changes: 2 additions & 0 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export type { Snippet, Tag, Category, Template } from './template';
export type {
SortingKey,
SortingOption,
TemplateEditRequest,
TemplateListResponse,
TemplateUploadRequest,
Expand Down

0 comments on commit ef4a7cd

Please sign in to comment.