Skip to content

Commit

Permalink
검색 결과에 정렬 기능 추가 (#255)
Browse files Browse the repository at this point in the history
* ✨ #245 - 정렬 관련 상수 선언 및 타입 정의

* ✨ #254 - useDiary에 sortBy 매개변수 추가

* ✨ #254 - 검색 결과 정렬 기능 구현

* ♻️ #254 - 정렬 기준 변경 시 정렬 목록을 순회하며 연산하는 코드 개선

* ♻️ #254 - 정렬 목록 초기값을 복사하여 useState에 적용
  • Loading branch information
Bori-github authored Jun 23, 2024
1 parent b6997c7 commit 8ccf30a
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 41 deletions.
2 changes: 2 additions & 0 deletions src/api/diaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import axios from 'lib/axios';
export const getDiaries = async ({
currentPage,
searchKeyword,
sortBy,
}: GetDiariesRequest) => {
const currentPageIndex = currentPage - 1;
const {
Expand All @@ -25,6 +26,7 @@ export const getDiaries = async ({
skip: PAGE_SIZE * currentPageIndex,
take: PAGE_SIZE,
searchKeyword,
sortBy,
},
});

Expand Down
59 changes: 24 additions & 35 deletions src/components/search/SearchResultHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,40 @@
import styled from '@emotion/styled';
import { useState } from 'react';
import { useCallback, type Dispatch, type SetStateAction } from 'react';
import type { SortByOption } from 'types/search';
import { ArrowDownIcon, CheckIcon } from 'assets/icons';
import { Popover } from 'components/common';
import { useClickOutside } from 'hooks/common';

interface SearchResultHeaderProps {
totalCount: number;
selectedSortOption: SortByOption;
sortOptions: SortByOption[];
setSortOptions: Dispatch<SetStateAction<SortByOption[]>>;
}

interface SortOptions {
id: string;
title: string;
selected: boolean;
}

const initialSortOptions = [
{
id: 'latest',
title: '최신순',
selected: true,
},
{
id: 'comments',
title: '댓글순',
selected: false,
},
];

export const SearchResultHeader = ({ totalCount }: SearchResultHeaderProps) => {
const [sortOptions, setSortOptions] =
useState<SortOptions[]>(initialSortOptions);

export const SearchResultHeader = ({
totalCount,
selectedSortOption,
sortOptions,
setSortOptions,
}: SearchResultHeaderProps) => {
const { ref, isVisible, setIsVisible } = useClickOutside();

const handleSortSearchResult = () => {
setIsVisible((state) => !state);
};

// TODO: 정렬 기능 추가
const handleSelectSortOption = (selectedIndex: number) => () => {
setSortOptions((prevState) => {
return prevState.map((state, stateIndex) => ({
...state,
selected: stateIndex === selectedIndex,
}));
});
};
const handleSelectSortOption = useCallback(
(selectedIndex: number) => () => {
setSortOptions((prevState) => {
return prevState.map((state, stateIndex) => ({
...state,
selected: stateIndex === selectedIndex,
}));
});
},
[],
);

return (
<Container>
Expand All @@ -55,11 +44,11 @@ export const SearchResultHeader = ({ totalCount }: SearchResultHeaderProps) => {
type="button"
onClick={handleSortSearchResult}
>
{sortOptions.find((option) => option.selected)?.title}
{selectedSortOption.title}
<ArrowDownIcon />
</SortSearchResultButton>
{isVisible && (
<Popover right={20}>
<Popover top={50} right={20}>
<ul>
{sortOptions.map((option, index) => {
const { id, selected, title } = option;
Expand Down
1 change: 1 addition & 0 deletions src/constants/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './sortBy';
23 changes: 23 additions & 0 deletions src/constants/search/sortBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const SORT_BY_ID = {
latest: 'latest',
popularity: 'popularity',
comments: 'comments',
} as const;

export const INITIAL_SORT_BY_LIST = [
{
id: SORT_BY_ID.latest,
title: '최신순',
selected: true,
},
{
id: SORT_BY_ID.popularity,
title: '인기순',
selected: false,
},
{
id: SORT_BY_ID.comments,
title: '댓글순',
selected: false,
},
] as const;
6 changes: 4 additions & 2 deletions src/hooks/services/queries/useDiaries.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import type { SortByType } from 'types/search';
import * as api from 'api';
import { queryKeys } from 'constants/services';

export const useDiaries = (searchKeyword?: string) => {
export const useDiaries = (searchKeyword?: string, sortBy?: SortByType) => {
const { data, isFetching, isFetchingNextPage, isError, fetchNextPage } =
useInfiniteQuery({
queryKey: [queryKeys.diaries, searchKeyword],
queryKey: [queryKeys.diaries, searchKeyword, sortBy],
queryFn: async ({ pageParam = 1 }) =>
await api.getDiaries({
currentPage: pageParam as number,
searchKeyword,
sortBy,
}),
getNextPageParam: (lastPage) => lastPage.nextPage,
});
Expand Down
24 changes: 20 additions & 4 deletions src/pages/search/[keyword].tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import styled from '@emotion/styled';
import { getServerSession } from 'next-auth';
import { useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import type {
GetServerSideProps,
InferGetServerSidePropsType,
NextPage,
} from 'next';
import type { SearchForm } from 'types/search';
import type { SearchForm, SortByOption } from 'types/search';
import { ObserverTarget, Seo } from 'components/common';
import { DiariesContainer } from 'components/diary';
import {
Expand All @@ -15,17 +16,29 @@ import {
SearchResultHeader,
} from 'components/search';
import { PAGE_PATH } from 'constants/common';
import { INITIAL_SORT_BY_LIST } from 'constants/search';
import { useIntersectionObserver } from 'hooks/common';
import { useDiaries } from 'hooks/services';
import { authOptions } from 'pages/api/auth/[...nextauth]';

const SearchResultPage: NextPage<
InferGetServerSidePropsType<typeof getServerSideProps>
> = ({ keyword }) => {
const [sortOptions, setSortOptions] = useState<SortByOption[]>([
...INITIAL_SORT_BY_LIST,
]);

const selectedSortOption = useMemo(
() => sortOptions.find((option) => option.selected) ?? sortOptions[0],
[sortOptions],
);

const methods = useForm<SearchForm>({ mode: 'onChange' });

const { diariesData, isLoading, isError, fetchNextPage } =
useDiaries(keyword);
const { diariesData, isLoading, isError, fetchNextPage } = useDiaries(
keyword,
selectedSortOption.id,
);
const { setTargetRef } = useIntersectionObserver({
onIntersect: fetchNextPage,
});
Expand All @@ -49,7 +62,10 @@ const SearchResultPage: NextPage<
}
header={
<SearchResultHeader
totalCount={diariesData[0].totalCount ?? 0}
totalCount={diariesData[0].totalCount}
selectedSortOption={selectedSortOption}
sortOptions={sortOptions}
setSortOptions={setSortOptions}
/>
}
highlightKeyword={keyword}
Expand Down
2 changes: 2 additions & 0 deletions src/types/diary.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SortByType } from './search';
import type { AxiosRequestConfig } from 'axios';
import type { User } from 'next-auth';

Expand Down Expand Up @@ -33,6 +34,7 @@ export type DiaryForm = Pick<
export interface GetDiariesRequest {
currentPage: number;
searchKeyword?: string;
sortBy?: SortByType;
}

export interface GetDiariesByUsernameRequest extends GetDiariesRequest {
Expand Down
10 changes: 10 additions & 0 deletions src/types/search.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
import type { SORT_BY_ID } from 'constants/search';

export interface SearchForm {
searchKeyword: string;
}

export type SortByType = keyof typeof SORT_BY_ID;

export interface SortByOption {
id: SortByType;
title: string;
selected: boolean;
}

0 comments on commit 8ccf30a

Please sign in to comment.