diff --git a/src/components/Buyer/BuyerSearchResult/OpenConsultResults.tsx b/src/components/Buyer/BuyerSearchResult/OpenConsultResults.tsx new file mode 100644 index 00000000..6e3e1c51 --- /dev/null +++ b/src/components/Buyer/BuyerSearchResult/OpenConsultResults.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import styled from 'styled-components'; +import { openConsultApiObject } from 'pages/Buyer/BuyerConsult'; +import SavedOpenConsultCard from '../BuyerSavedCounselor.tsx/SavedOpenConsultCard'; +interface OpenConsultResultProps { + openConsultList: openConsultApiObject[]; +} +function OpenConsultResults({ openConsultList }: OpenConsultResultProps) { + return ( + + {openConsultList.map((item) => ( + + ))} + + ); +} + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + gap: 1.2rem; + align-items: center; + width: 100%; +`; +export default OpenConsultResults; diff --git a/src/components/Buyer/Common/OpenConsultSortModal.tsx b/src/components/Buyer/Common/OpenConsultSortModal.tsx new file mode 100644 index 00000000..ee39bdce --- /dev/null +++ b/src/components/Buyer/Common/OpenConsultSortModal.tsx @@ -0,0 +1,136 @@ +import { ReactComponent as CheckIcon } from 'assets/icons/icon-modal-check.svg'; +import { SetStateAction } from 'react'; +import { useRecoilState, useSetRecoilState } from 'recoil'; +import styled, { keyframes } from 'styled-components'; +import { Green, Grey1, Grey4, Grey6 } from 'styles/color'; +import { Body1 } from 'styles/font'; +import { isSortModalOpenState, scrollLockState } from 'utils/atom'; +interface SortModalProps { + sortType: number; + setSortType: React.Dispatch>; + setPostId: React.Dispatch>; +} +//최근순 인기순 별점순 모달 +export const OpenConsultSortModal = ({ + sortType, + setSortType, + setPostId, +}: SortModalProps) => { + //modal 여부 + const [isModalOpen, setIsModalOpen] = useRecoilState(isSortModalOpenState); + //scorll 막기 + const setScrollLock = useSetRecoilState(scrollLockState); + + return ( + +
+ +
+
{ + setSortType(0); + setPostId(0); + setIsModalOpen(false); + setScrollLock(false); + }} + > + {sortType === 0 ? ( + <> + 최근순 + + + ) : ( + 최근순 + )} +
+
{ + setSortType(1); + setPostId(0); + setIsModalOpen(false); + setScrollLock(false); + }} + > + {sortType === 1 ? ( + <> + 공감 많은 순 + + + ) : ( + 공감 많은 순 + )} +
+
{ + setSortType(2); + setPostId(0); + setIsModalOpen(false); + setScrollLock(false); + }} + > + {sortType === 2 ? ( + <> + 댓글 많은 순 + + + ) : ( + 댓글 많은 순 + )} +
+
+ ); +}; +const slideIn = keyframes` + from{ + transform : translateY(100%); + } + to{ + transform : translateY(0%); + } +`; +const slideOut = keyframes` + from{ + transform : translateY(0%); + } + to{ + transform : translateY(100%); + } +`; +const Wrapper = styled.div<{ visible: boolean }>` + @media (max-width: 767px) { + width: 100vw; + } + @media (min-width: 768px) { + width: 37.5rem; + } + position: fixed; + height: 22.7rem; + background-color: ${Grey6}; + bottom: 0; + border-radius: 2rem 2rem 0 0; + box-shadow: 0px -4px 10px rgba(0, 0, 0, 0.1); + z-index: 2002; + animation: ${({ visible }) => (visible ? slideIn : slideOut)} 0.3s ease-in-out; + + .bar-wrapper { + height: 4.5rem; + display: flex; + justify-content: center; + } + .row { + display: flex; + padding: 1rem 2rem 0 2rem; + height: 4.4rem; + justify-content: space-between; + cursor: pointer; + } +`; +const Bar = styled.div` + margin-top: 1.2rem; + width: 3.1rem; + height: 0.3rem; + background-color: ${Grey4}; +`; diff --git a/src/components/Common/Divider2.tsx b/src/components/Common/Divider2.tsx index 06f84e8b..70ed5297 100644 --- a/src/components/Common/Divider2.tsx +++ b/src/components/Common/Divider2.tsx @@ -53,8 +53,6 @@ const Wrapper = styled.nav` display: flex; justify-content: center; border-bottom: 1px solid ${Grey6}; - position: sticky; - top: 5.3rem; background-color: white; z-index: 999; `; diff --git a/src/pages/Buyer/BuyerSearchResult.tsx b/src/pages/Buyer/BuyerSearchResult.tsx index efa3ef98..6a301c26 100644 --- a/src/pages/Buyer/BuyerSearchResult.tsx +++ b/src/pages/Buyer/BuyerSearchResult.tsx @@ -8,10 +8,9 @@ import { ReactComponent as Down } from 'assets/icons/icon-drop-down.svg'; import { SearchResults } from 'components/Buyer/BuyerSearchResult/SearchResults'; import { SortModal } from 'components/Buyer/Common/SortModal'; import { ChangeEvent, useLayoutEffect, useRef, useState } from 'react'; -import { sortList } from 'utils/constant'; +import { openSortList, sortList } from 'utils/constant'; import { useRecoilState, useSetRecoilState } from 'recoil'; import { - isLoadingState, isSortModalOpenState, scrollLockState, searchKeywordState, @@ -19,13 +18,18 @@ import { import Input from 'components/Common/Input'; import { patchSearchWordsCounselorsResults, - patchSearchWordsResults, + patchSearchWordsPostsResults, } from 'api/patch'; import { SearchResultData } from 'utils/type'; import { ConverSortType } from 'utils/convertSortType'; import { ReactComponent as Empty } from 'assets/icons/graphic-noting.svg'; import { LoadingSpinner } from 'utils/LoadingSpinner'; import useIntersectionObserver from 'hooks/useIntersectionObserver'; +import Divider2 from 'components/Common/Divider2'; +import OpenConsultResults from 'components/Buyer/BuyerSearchResult/OpenConsultResults'; +import { openConsultApiObject } from './BuyerConsult'; +import { OpenConsultSortModal } from 'components/Buyer/Common/OpenConsultSortModal'; +import { ConverOpenSortType } from 'utils/convertOpenSortType'; export const BuyerSearchResult = () => { const navigate = useNavigate(); //0 : 최신순 1:인기순 2: 별점순 @@ -43,8 +47,14 @@ export const BuyerSearchResult = () => { const [input, setInput] = useState(initInput); //결과저장 const [searchData, setSearchData] = useState([]); + const [openConsultSearchData, setOpenConsultSearchData] = useState< + openConsultApiObject[] + >([]); //무한스크롤 위한 page num const [pageNum, setPageNum] = useState(0); + const [lastId, setLastId] = useState(0); + // 상담사 탭 0, 공개 상담 1 + const [tabState, setTabState] = useState(1); const [isLastElem, setIsLastElem] = useState(false); const preventRef = useRef(true); // 중복 방지 옵션 const onIntersect: IntersectionObserverCallback = async (entry) => { @@ -55,7 +65,12 @@ export const BuyerSearchResult = () => { preventRef.current ) { preventRef.current = false; - await fetchSearchResults(keyword, pageNum); + if (tabState === 1) { + await fetchSearchResults(keyword, pageNum); + } else if (tabState === 2) { + await fetchOpenSearchResults(keyword, lastId); + } + preventRef.current = true; } }; @@ -112,13 +127,51 @@ export const BuyerSearchResult = () => { } } }; + + const fetchOpenSearchResults = async (searchWord: string, postId: number) => { + try { + const body = { + word: searchWord, + postId: postId, + }; + const sortTypeString: string = ConverOpenSortType(sortType); + const res: any = await patchSearchWordsPostsResults(sortTypeString, body); + if (res.status === 200) { + if (res.data.length !== 0) { + if (postId === 0) { + setOpenConsultSearchData(res.data); + setPageNum(pageNum + 1); + } else { + const updatedSearchs = [...openConsultSearchData, ...res.data]; + setLastId(res.data.postId); + setOpenConsultSearchData(updatedSearchs); + } + setIsLastElem(true); + } else { + setOpenConsultSearchData([]); + } + } else if (res.response.status === 400) { + alert('검색어는 2~20자 사이여야 합니다.'); + } + } catch (e) { + alert(e); + } finally { + if (postId === 0) { + setIsLoading(false); + } + } + }; useLayoutEffect(() => { setIsLastElem(false); setPageNum(0); setSearchData([]); setIsLoading(true); - fetchSearchResults(keyword, 0); - }, [keyword, sortType]); + if (tabState === 1) { + fetchSearchResults(keyword, 0); + } else if (tabState === 2) { + fetchOpenSearchResults(keyword, 0); + } + }, [keyword, sortType, tabState]); if (isLoading) { return ( <> @@ -142,105 +195,195 @@ export const BuyerSearchResult = () => { ); } else { - return ( - - - { - navigate('/search'); - }} - /> - - - - - -
-
{ - setIsModalOpen(true); - setScrollLock(true); - }} - > - {sortList[sortType]} - -
-
- {searchData.length !== 0 ? ( - <> - - {!isLastElem ? ( -
- ) : ( -
- )} - - ) : ( - - - - 검색 결과가 없어요. - - 마인더명, 제목, 상담 스타일 등 - 더 간단한 단어로 검색해보세요. - - )} + if (tabState === 1) { + return ( + + + + { + navigate('/search'); + }} + /> + + + + + + +
+
{ + setIsModalOpen(true); + setScrollLock(true); + }} + > + {sortList[sortType]} + +
+
+
- {isModalOpen ? ( - <> - { - //여기서 api 호출 - setIsModalOpen(false); - setScrollLock(false); - }} - /> - - - ) : null} -
- ); + {searchData.length !== 0 ? ( + <> + + {!isLastElem ? ( +
+ ) : ( +
+ )} + + ) : ( + + + + 검색 결과가 없어요. + + 마인더명, 제목, 상담 스타일 등 + 더 간단한 단어로 검색해보세요. + + )} + + {isModalOpen ? ( + <> + { + //여기서 api 호출 + setIsModalOpen(false); + setScrollLock(false); + }} + /> + + + ) : null} + + ); + } else if (tabState === 2) { + return ( + + + + { + navigate('/search'); + }} + /> + + + + + + +
+
{ + setIsModalOpen(true); + setScrollLock(true); + }} + > + {openSortList[sortType]} + +
+
+
+ + {openConsultSearchData.length !== 0 ? ( + <> + + {!isLastElem ? ( +
+ ) : ( +
+ )} + + ) : ( + + + + 검색 결과가 없어요. + + 마인더명, 제목, 상담 스타일 등 + 더 간단한 단어로 검색해보세요. + + )} + + {isModalOpen ? ( + <> + { + //여기서 api 호출 + setIsModalOpen(false); + setScrollLock(false); + }} + /> + + + ) : null} + + ); + } } }; const Wrapper = styled.div` .select { display: flex; + background-color: white; height: 4.4rem; padding: 0.4rem 2rem; align-items: center; justify-content: flex-end; cursor: pointer; + z-index: 9; } .select-wrapper { display: flex; gap: 0.4rem; } `; -const HeaderWrapper = styled.div` +const HeaderWrapper = styled.header` height: 5.2rem; gap: 0.8rem; background-color: ${White}; position: relative; display: flex; align-items: center; + top: 0; + width: 100%; justify-content: center; box-sizing: border-box; padding: 0.4rem 2rem; @@ -281,3 +424,11 @@ const EmptyWrapper = styled.div` flex-direction: column; align-items: center; `; + +const FixedContainer = styled.section` + position: sticky; + width: 100%; + top: 0; + background-color: white; + z-index: 99; +`; diff --git a/src/utils/constant.ts b/src/utils/constant.ts index 909cea7b..0513e658 100644 --- a/src/utils/constant.ts +++ b/src/utils/constant.ts @@ -13,6 +13,7 @@ export const categories = [ '조언', '팩폭', ]; +export const openSortList = ['최근순', '공감 많은 순', '댓글 많은 순']; export const sortList = ['최근순', '인기순', '별점순']; export const quizList = [ diff --git a/src/utils/convertOpenSortType.ts b/src/utils/convertOpenSortType.ts new file mode 100644 index 00000000..c8e82a2a --- /dev/null +++ b/src/utils/convertOpenSortType.ts @@ -0,0 +1,11 @@ +export const ConverOpenSortType = (typeNum: number) => { + if (typeNum === 0) { + return 'LATEST'; + } else if (typeNum === 1) { + return 'DESC_TOTAL_COMMENT'; + } else if (typeNum === 2) { + return 'DESC_TOTAL_LIKE'; + } else { + return ''; + } +};