diff --git a/src/api/words/index.ts b/src/api/words/index.ts new file mode 100644 index 00000000..e7b0ebaf --- /dev/null +++ b/src/api/words/index.ts @@ -0,0 +1,13 @@ +import { get } from '@/lib/axios' +import { SuccessResponse } from '@/types/response' +import { FilterType, SimpleWordType } from '@/types/word' + +export async function getAllWords( + category: FilterType, + sortBy: string | undefined, +) { + const res = await get>( + `/words?category=${category}&sortBy=${sortBy}`, + ) + return res.words +} diff --git a/src/app/dictionary/page.tsx b/src/app/dictionary/page.tsx index eb0d4892..0b20db1d 100644 --- a/src/app/dictionary/page.tsx +++ b/src/app/dictionary/page.tsx @@ -1,26 +1,45 @@ 'use client' +import SortButton from '@/components/common/SortButton' import TabFilter from '@/components/domain/dictionary/TabFilter' import WordsList from '@/components/domain/dictionary/WordsList' +import FilterBottomSheet from '@/components/shared/FilterBottomSheet' import SearchHeader from '@/components/shared/SearchHeader' import TopButton from '@/components/shared/TopButton' -import { FilterType } from '@/types/word' +import { sortValue, useGetAllWords } from '@/hooks/word/useGetAllWords' +import useUIStore from '@/store/useUIStore' +import { FilterType, SimpleWordType, WordSortType } from '@/types/word' import { useSearchParams } from 'next/navigation' export default function DictionaryPage() { const searchParams = useSearchParams() - const totalCnt = 100 const filters: FilterType[] = ['전체', '개발', '디자인', '비즈니스'] - const category: any = searchParams.get('category') ?? '전체' + const { bottomSheetType, openBottomSheet } = useUIStore() + + const category: string = searchParams.get('category') ?? '전체' + const sortBy: string = searchParams.get('sortBy') ?? 'name' + const { words } = useGetAllWords( + category as FilterType, + sortBy as WordSortType, + ) return ( <>
-

- 등록된 용어 - {totalCnt} -

+
+

+ 등록된 용어 + + {words && words.length} + +

+ openBottomSheet('filter')} + /> +
+
{filters.map((filter: FilterType, idx: number) => (
- +
+ ) } diff --git a/src/components/common/SortButton/index.tsx b/src/components/common/SortButton/index.tsx index 947a215f..a6916b05 100644 --- a/src/components/common/SortButton/index.tsx +++ b/src/components/common/SortButton/index.tsx @@ -1,14 +1,16 @@ +'use client' import Image from 'next/image' +import { MouseEventHandler } from 'react' type SortButtonProps = { - text: string - onClick: () => void + sortBy: string + onClick: MouseEventHandler } -export default function SortButton({ text, onClick }: SortButtonProps) { +export default function SortButton({ sortBy, onClick }: SortButtonProps) { return (
-

{text}

+

{sortBy}

open
) diff --git a/src/components/domain/dictionary/WordsList/index.tsx b/src/components/domain/dictionary/WordsList/index.tsx index cae1ef1a..1c5e615f 100644 --- a/src/components/domain/dictionary/WordsList/index.tsx +++ b/src/components/domain/dictionary/WordsList/index.tsx @@ -1,17 +1,17 @@ import WordListItem from '@/components/shared/WordListItem' -import { words } from '@/components/shared/WordListItem/data' -import { FilterType, SimpleWordType } from '@/types/word' +import { SimpleWordType } from '@/types/word' type WordsListProps = { - category: FilterType + words: SimpleWordType[] } -export default function WordsList({ category }: WordsListProps) { +export default function WordsList({ words }: WordsListProps) { return ( <> - {words.map((word: SimpleWordType, idx: number) => ( - - ))} + {words && + words.map((word: SimpleWordType, idx: number) => ( + + ))} ) } diff --git a/src/components/shared/FilterBottomSheet/index.tsx b/src/components/shared/FilterBottomSheet/index.tsx index 7130e201..05c26ab6 100644 --- a/src/components/shared/FilterBottomSheet/index.tsx +++ b/src/components/shared/FilterBottomSheet/index.tsx @@ -1,31 +1,35 @@ +'use client' import BottomSheet from '@/components/common/BottomSheet' import { FILTER_MENUS } from '@/constants/bottomSheet' import Image from 'next/image' import { cn } from '@/lib/core' import useUIStore from '@/store/useUIStore' +import useCreateQueryString from '@/hooks/useCreateQueryString' +import { WordSortType } from '@/types/word' +import { sortValue } from '@/hooks/word/useGetAllWords' export type BottomSheetProps = { isOpen: boolean target: 'words' | 'comments' selected: string - setSelected: (menu: string) => void } export default function FilterBottomSheet({ isOpen, target, selected, - setSelected, }: BottomSheetProps) { const { closeBottomSheet } = useUIStore() + const { replacePathname } = useCreateQueryString() const menuItems = FILTER_MENUS[target] if (!isOpen) return null - const handleClick = (menu: string) => { - setSelected(menu) + const handleClick = (menu: WordSortType) => { + replacePathname('sortBy', sortValue[menu] ?? 'name') closeBottomSheet() } + return ( {menuItems.map((menu, idx) => ( @@ -34,7 +38,7 @@ export default function FilterBottomSheet({ className={cn('flex justify-between py-6 list-none cursor-pointer', { 'text-primary-400': selected === menu, })} - onClick={() => handleClick(menu)} + onClick={() => handleClick(menu as WordSortType)} >

{menu}

{selected === menu && ( diff --git a/src/components/shared/TopButton/index.tsx b/src/components/shared/TopButton/index.tsx index 2152c53c..67aa860a 100644 --- a/src/components/shared/TopButton/index.tsx +++ b/src/components/shared/TopButton/index.tsx @@ -1,3 +1,4 @@ +'use client' import Image from 'next/image' import React from 'react' diff --git a/src/components/shared/WordListItem/data.ts b/src/components/shared/WordListItem/data.ts index 3585bc30..8d7c06c9 100644 --- a/src/components/shared/WordListItem/data.ts +++ b/src/components/shared/WordListItem/data.ts @@ -7,8 +7,8 @@ export const words: SimpleWordType[] = [ meaning: '기업의 이미지를 나타내는 로고나 디자인을 말해요.기업의 이미지를 나타내는 로고나 디자인을 말해요.기업의 이미지를 나타내는 로고나 디자인을 말해요기업의 이미지를 나타내는 로고나 디자인을 말해요', category: '디자인', - viewCnt: 83, - commentCnt: 9, + viewCount: 83, + commentCount: 9, }, { @@ -17,104 +17,104 @@ export const words: SimpleWordType[] = [ meaning: '투자한 자본 대비 수익을 얼마나 얻었는지 측정하는 지표예요 (이익 ÷ 투자액 × 100)', category: '비즈니스', - viewCnt: 150, - commentCnt: 20, + viewCount: 150, + commentCount: 20, }, { id: 2, name: 'B2B(Business-to-Business)', meaning: '기업 간 제품이나 서비스를 거래하는 비즈니스 모델이에요', category: '비즈니스', - viewCnt: 85, - commentCnt: 10, + viewCount: 85, + commentCount: 10, }, { id: 3, name: 'API', meaning: '응용 프로그램 간에 상호작용을 가능하게 하는 인터페이스예요', category: '개발', - viewCnt: 102, - commentCnt: 18, + viewCount: 102, + commentCount: 18, }, { id: 4, name: 'UX 디자인', meaning: '사용자 경험을 고려하여 제품이나 서비스를 설계하는 과정이에요', category: '디자인', - viewCnt: 145, - commentCnt: 22, + viewCount: 145, + commentCount: 22, }, { id: 5, name: 'P2P(Peer-to-Peer)', meaning: '개인 간에 직접적으로 상품이나 서비스를 거래하는 시스템이에요', category: '비즈니스', - viewCnt: 75, - commentCnt: 8, + viewCount: 75, + commentCount: 8, }, { id: 6, name: 'Git', meaning: '분산형 버전 관리 시스템으로, 소스 코드를 관리하는 데 사용돼요', category: '개발', - viewCnt: 98, - commentCnt: 15, + viewCount: 98, + commentCount: 15, }, { id: 7, name: '각출', meaning: '각자의 비용을 각자 지불하는 것을 의미해요', category: '비즈니스', - viewCnt: 13, - commentCnt: 5, + viewCount: 13, + commentCount: 5, }, { id: 8, name: 'CRM', meaning: '고객과의 관계를 관리하여 매출을 극대화하는 전략이에요', category: '비즈니스', - viewCnt: 95, - commentCnt: 12, + viewCount: 95, + commentCount: 12, }, { id: 9, name: 'ERP', meaning: '기업의 자원을 통합적으로 관리하기 위한 소프트웨어 시스템이에요', category: '비즈니스', - viewCnt: 110, - commentCnt: 18, + viewCount: 110, + commentCount: 18, }, { id: 10, name: 'SWOT 분석', meaning: '강점, 약점, 기회, 위협을 분석하여 전략을 수립하는 도구예요', category: '비즈니스', - viewCnt: 130, - commentCnt: 22, + viewCount: 130, + commentCount: 22, }, { id: 11, name: 'REST API', meaning: 'HTTP를 통해 통신하는 웹 서비스를 구축하기 위한 인터페이스예요', category: '개발', - viewCnt: 112, - commentCnt: 17, + viewCount: 112, + commentCount: 17, }, { id: 12, name: 'UI 디자인', meaning: '사용자 인터페이스를 설계하여 사용자 경험을 개선하는 작업이에요', category: '디자인', - viewCnt: 150, - commentCnt: 25, + viewCount: 150, + commentCount: 25, }, { id: 13, name: 'HTML', meaning: '웹 페이지의 구조를 정의하는 마크업 언어예요', category: '개발', - viewCnt: 90, - commentCnt: 10, + viewCount: 90, + commentCount: 10, }, { id: 14, @@ -122,24 +122,24 @@ export const words: SimpleWordType[] = [ meaning: '제품이나 서비스를 차별화하여 고유의 이미지로 자리잡게 하는 과정이에요', category: '비즈니스', - viewCnt: 135, - commentCnt: 20, + viewCount: 135, + commentCount: 20, }, { id: 15, name: '스크럼', meaning: '소프트웨어 개발에서 팀이 협력하여 작업을 수행하는 방법론이에요', category: '개발', - viewCnt: 125, - commentCnt: 16, + viewCount: 125, + commentCount: 16, }, { id: 16, name: '프로토타이핑', meaning: '제품이나 서비스의 초기 모델을 제작하여 테스트하는 과정이에요', category: '디자인', - viewCnt: 142, - commentCnt: 19, + viewCount: 142, + commentCount: 19, }, { id: 17, @@ -147,40 +147,40 @@ export const words: SimpleWordType[] = [ meaning: '인터넷을 통해 데이터와 프로그램을 저장하고 접근할 수 있는 기술이에요', category: '개발', - viewCnt: 138, - commentCnt: 23, + viewCount: 138, + commentCount: 23, }, { id: 18, name: '로고 디자인', meaning: '브랜드나 기업을 상징하는 이미지를 만드는 작업이에요', category: '디자인', - viewCnt: 92, - commentCnt: 11, + viewCount: 92, + commentCount: 11, }, { id: 19, name: '마케팅 전략', meaning: '제품이나 서비스를 효과적으로 판매하기 위한 계획과 방법을 말해요', category: '비즈니스', - viewCnt: 167, - commentCnt: 30, + viewCount: 167, + commentCount: 30, }, { id: 20, name: 'DevOps', meaning: '소프트웨어 개발과 운영을 통합하여 효율성을 높이는 방법이에요', category: '개발', - viewCnt: 144, - commentCnt: 21, + viewCount: 144, + commentCount: 21, }, { id: 21, name: '브랜드 아이덴티티', meaning: '브랜드가 지닌 고유한 이미지와 가치를 나타내는 요소를 말해요', category: '디자인', - viewCnt: 136, - commentCnt: 18, + viewCount: 136, + commentCount: 18, }, { id: 22, @@ -188,24 +188,24 @@ export const words: SimpleWordType[] = [ meaning: '검색 엔진에서 웹사이트의 노출을 증가시키기 위한 최적화 작업이에요', category: '비즈니스', - viewCnt: 154, - commentCnt: 24, + viewCount: 154, + commentCount: 24, }, { id: 23, name: '테스트 주도 개발', meaning: '테스트 케이스를 먼저 작성하고 소프트웨어를 개발하는 방법론이에요', category: '개발', - viewCnt: 118, - commentCnt: 15, + viewCount: 118, + commentCount: 15, }, { id: 24, name: '모바일 퍼스트 디자인', meaning: '모바일 기기에서의 사용성을 우선으로 고려하여 설계하는 방법이에요', category: '디자인', - viewCnt: 123, - commentCnt: 20, + viewCount: 123, + commentCount: 20, }, { id: 25, @@ -213,8 +213,8 @@ export const words: SimpleWordType[] = [ meaning: '고객을 다양한 기준에 따라 세분화하여 마케팅 전략을 수립하는 과정이에요', category: '비즈니스', - viewCnt: 121, - commentCnt: 13, + viewCount: 121, + commentCount: 13, }, { id: 26, @@ -222,88 +222,88 @@ export const words: SimpleWordType[] = [ meaning: '유연한 대응과 반복적 개선을 강조하는 소프트웨어 개발 방법론이에요', category: '개발', - viewCnt: 110, - commentCnt: 18, + viewCount: 110, + commentCount: 18, }, { id: 27, name: '리스크 관리', meaning: '기업이 직면할 수 있는 위험을 식별하고 대응하는 과정이에요', category: '비즈니스', - viewCnt: 98, - commentCnt: 12, + viewCount: 98, + commentCount: 12, }, { id: 28, name: '트렌드 분석', meaning: '시장의 변화를 분석하고 미래의 흐름을 예측하는 과정이에요', category: '비즈니스', - viewCnt: 145, - commentCnt: 20, + viewCount: 145, + commentCount: 20, }, { id: 29, name: '그로스 해킹', meaning: '성장을 촉진하기 위한 창의적이고 분석적인 마케팅 기법이에요', category: '비즈니스', - viewCnt: 158, - commentCnt: 27, + viewCount: 158, + commentCount: 27, }, { id: 30, name: '백엔드 개발', meaning: '서버와 데이터베이스를 관리하고 처리하는 개발 영역이에요', category: '개발', - viewCnt: 120, - commentCnt: 14, + viewCount: 120, + commentCount: 14, }, { id: 31, name: '비주얼 아이덴티티', meaning: '브랜드의 시각적 요소들을 통해 전달하는 이미지예요', category: '디자인', - viewCnt: 110, - commentCnt: 15, + viewCount: 110, + commentCount: 15, }, { id: 32, name: '핀테크', meaning: '금융과 기술을 결합해 혁신적인 금융 서비스를 제공하는 산업이에요', category: '비즈니스', - viewCnt: 132, - commentCnt: 19, + viewCount: 132, + commentCount: 19, }, { id: 33, name: '데이터 마이닝', meaning: '대량의 데이터를 분석해 유용한 정보를 추출하는 과정이에요', category: '개발', - viewCnt: 140, - commentCnt: 18, + viewCount: 140, + commentCount: 18, }, { id: 34, name: '와이어프레임', meaning: '웹사이트나 앱의 기본 구조를 시각적으로 표현한 설계도예요', category: '디자인', - viewCnt: 122, - commentCnt: 16, + viewCount: 122, + commentCount: 16, }, { id: 35, name: '비즈니스 모델', meaning: '기업이 가치를 창출하고 수익을 창출하는 방법과 구조예요', category: '비즈니스', - viewCnt: 165, - commentCnt: 22, + viewCount: 165, + commentCount: 22, }, { id: 36, name: '프론트엔드 개발', meaning: '사용자가 직접 상호작용하는 웹사이트의 부분을 개발하는 영역이에요', category: '개발', - viewCnt: 138, - commentCnt: 20, + viewCount: 138, + commentCount: 20, }, { id: 37, @@ -311,32 +311,32 @@ export const words: SimpleWordType[] = [ meaning: '사용자의 요구와 시장의 필요를 반영하여 제품을 설계하는 작업이에요', category: '디자인', - viewCnt: 128, - commentCnt: 17, + viewCount: 128, + commentCount: 17, }, { id: 38, name: '서스테이너빌리티', meaning: '기업 활동이 환경과 사회에 미치는 영향을 최소화하는 전략이에요', category: '비즈니스', - viewCnt: 150, - commentCnt: 25, + viewCount: 150, + commentCount: 25, }, { id: 39, name: '머신 러닝', meaning: '컴퓨터가 데이터를 학습하여 스스로 성능을 향상시키는 기술이에요', category: '개발', - viewCnt: 142, - commentCnt: 21, + viewCount: 142, + commentCount: 21, }, { id: 40, name: '컬러 팔레트', meaning: '브랜드나 디자인 프로젝트에 사용되는 색상의 조합을 말해요', category: '디자인', - viewCnt: 115, - commentCnt: 12, + viewCount: 115, + commentCount: 12, }, { id: 41, @@ -344,24 +344,24 @@ export const words: SimpleWordType[] = [ meaning: '타사의 우수한 사례를 참고하여 자사의 성과를 향상시키는 기법이에요', category: '비즈니스', - viewCnt: 136, - commentCnt: 16, + viewCount: 136, + commentCount: 16, }, { id: 42, name: '풀스택 개발', meaning: '프론트엔드와 백엔드를 모두 다룰 수 있는 개발자를 의미해요', category: '개발', - viewCnt: 148, - commentCnt: 23, + viewCount: 148, + commentCount: 23, }, { id: 43, name: '타이포그래피', meaning: '문자의 배열과 디자인을 통해 시각적 효과를 극대화하는 기술이에요', category: '디자인', - viewCnt: 124, - commentCnt: 14, + viewCount: 124, + commentCount: 14, }, { id: 44, @@ -369,16 +369,16 @@ export const words: SimpleWordType[] = [ meaning: '온라인 플랫폼을 통해 상품과 서비스를 거래하는 전자 상거래를 의미해요', category: '비즈니스', - viewCnt: 167, - commentCnt: 28, + viewCount: 167, + commentCount: 28, }, { id: 45, name: '버전 관리', meaning: '소스 코드의 변경 이력을 관리하고 추적하는 시스템이에요', category: '개발', - viewCnt: 110, - commentCnt: 12, + viewCount: 110, + commentCount: 12, }, { id: 46, @@ -386,24 +386,24 @@ export const words: SimpleWordType[] = [ meaning: '정보를 시각적으로 표현하여 이해하기 쉽게 전달하는 디자인 방식이에요', category: '디자인', - viewCnt: 152, - commentCnt: 19, + viewCount: 152, + commentCount: 19, }, { id: 47, name: '고객 관계 관리', meaning: '고객과의 장기적인 관계를 구축하고 유지하는 비즈니스 전략이에요', category: '비즈니스', - viewCnt: 139, - commentCnt: 24, + viewCount: 139, + commentCount: 24, }, { id: 48, name: '클린 코드', meaning: '가독성과 유지보수성이 높은 코드를 작성하는 개발 원칙이에요', category: '개발', - viewCnt: 119, - commentCnt: 15, + viewCount: 119, + commentCount: 15, }, { id: 49, @@ -411,15 +411,15 @@ export const words: SimpleWordType[] = [ meaning: '일관된 사용자 경험을 위해 디자인 규칙과 패턴을 체계화한 시스템이에요', category: '디자인', - viewCnt: 145, - commentCnt: 21, + viewCount: 145, + commentCount: 21, }, { id: 50, name: '네트워킹', meaning: '사업적 또는 개인적 관계를 구축하고 유지하는 활동을 의미해요', category: '비즈니스', - viewCnt: 132, - commentCnt: 18, + viewCount: 132, + commentCount: 18, }, ] diff --git a/src/components/shared/WordListItem/index.tsx b/src/components/shared/WordListItem/index.tsx index 4d020f1c..3a83c9a9 100644 --- a/src/components/shared/WordListItem/index.tsx +++ b/src/components/shared/WordListItem/index.tsx @@ -11,7 +11,7 @@ type WordListItemProps = { } export default function WordListItem({ - word: { id, category, name, meaning, viewCnt, commentCnt }, + word: { id, category, name, meaning, viewCount, commentCount }, showBookmarkBtn = false, isMarked, }: WordListItemProps) { @@ -38,7 +38,7 @@ export default function WordListItem({
view -

{viewCnt}

+

{viewCount}

-

{commentCnt}

+

{commentCount}

diff --git a/src/hooks/useCreateQueryString.tsx b/src/hooks/useCreateQueryString.tsx new file mode 100644 index 00000000..4c9c618e --- /dev/null +++ b/src/hooks/useCreateQueryString.tsx @@ -0,0 +1,23 @@ +'use client' +import { usePathname, useRouter, useSearchParams } from 'next/navigation' +import { useCallback } from 'react' + +export default function useCreateQueryString() { + const router = useRouter() + const pathname = usePathname() + const searchParams = useSearchParams() + + const createQueryString = useCallback( + (key: string, value: string) => { + const params = new URLSearchParams(searchParams.toString()) + params.set(key, value) + return params.toString() + }, + [searchParams], + ) + + const replacePathname = (key: string, value: string) => { + return router.replace(pathname + '?' + createQueryString(key, value)) + } + return { replacePathname } +} diff --git a/src/hooks/word/useGetAllWords.ts b/src/hooks/word/useGetAllWords.ts new file mode 100644 index 00000000..011b1aa7 --- /dev/null +++ b/src/hooks/word/useGetAllWords.ts @@ -0,0 +1,19 @@ +import { getAllWords } from '@/api/words' +import { FilterType, WordSortType } from '@/types/word' +import { useQuery } from '@tanstack/react-query' + +export const sortValue: any = { + 사전순: 'name', + name: '사전순', + 조회순: 'viewCount', + viewCount: '조회순', +} + +export const useGetAllWords = (category: FilterType, sortBy: WordSortType) => { + const { data: words, isLoading } = useQuery({ + queryKey: ['words', category, sortBy], + queryFn: () => getAllWords(category, sortValue[sortBy]), + }) + + return { words, isLoading } +} diff --git a/src/provider/QueryProvider.tsx b/src/provider/QueryProvider.tsx index 518cf135..83f2335f 100644 --- a/src/provider/QueryProvider.tsx +++ b/src/provider/QueryProvider.tsx @@ -8,6 +8,7 @@ const queryClient = new QueryClient({ retry: 1, refetchOnMount: false, refetchOnWindowFocus: false, + staleTime: 1000 * 60 * 5, }, }, }) diff --git a/src/types/response.ts b/src/types/response.ts index 22500b33..cd3be6f7 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -1,6 +1,6 @@ export type SuccessResponse = { code: string - data: T + [key: string]: T | string } export type FailResponse = { diff --git a/src/types/word.ts b/src/types/word.ts index e705fd55..761e6356 100644 --- a/src/types/word.ts +++ b/src/types/word.ts @@ -4,27 +4,24 @@ export type CategoryType = { export type FilterType = '전체' | CategoryType['category'] +export type WordSortType = 'name' | 'viewCount' + export type SimpleWordType = { id: number name: string meaning: string - viewCnt: number - commentCnt: number + viewCount: number + commentCount: number } & CategoryType export type DetailWordType = { - id: number - name: string pronunciationInfo: { english: string } - meaning: string - viewCount: number - commentCount: number isMarked: boolean - markedCount: number + bookmarkCount: number example: { text: string; source: string; createdAt: Date }[] source: string createdAt: Date updatedAt: Date -} & CategoryType +} & SimpleWordType