diff --git a/src/api/detail.ts b/src/api/detail.ts index c17599a0..d2b112d0 100644 --- a/src/api/detail.ts +++ b/src/api/detail.ts @@ -25,8 +25,8 @@ export const getIsWish = async (id: number) => { const response = await axios.get(`/api/wishes/${id}`, { withCredentials: true, }); - const data: boolean = response.data; - return data; + const data = response.data; + return data.data; } catch (error) { if (axios.isAxiosError(error)) { console.log(error); diff --git a/src/api/search.ts b/src/api/search.ts index eea17384..506644c9 100644 --- a/src/api/search.ts +++ b/src/api/search.ts @@ -3,68 +3,77 @@ import {Dispatch} from 'react'; import {translateCategoryCode, translateLocation, translateSort} from '@/utils/translateSearchData'; -import {DataType, Keywords, Popular, Search, SearchHotItemType, SearchItemType, SearchKeywordType} from '@/types/home'; +import {DataType, Keywords, Popular, Search, SearchHotItemType, SearchKeywordType} from '@/types/home'; -export async function search( - keyword: string, - location: string, - sort: string, - set: Dispatch>, -) { +interface PramsType { + keyword?: string; + page: number; + size: number; + sort: string; + categoryCode?: string; + areaCode?: number; + sigunguCode?: number; +} + +export async function search(keyword: string, location: string, sort: string, page: number) { try { const searchLocation = translateLocation(location); + const params: PramsType = { + keyword: keyword, + page: page, + size: 20, + sort: translateSort(sort), + }; + if (searchLocation.areaCode !== 0) { + params.areaCode = searchLocation.areaCode; + if (searchLocation.sigunguCode !== 0) { + params.sigunguCode = searchLocation.sigunguCode; + } + } const fetchData = await axios.get('/api/places/search', { - params: { - page: 0, - size: 20, - areaCode: searchLocation.areaCode, - sigunguCode: searchLocation.sigunguCode, - keyword: keyword, - sort: translateSort(sort), - }, + params: params, }); const data: DataType = fetchData.data; - console.log(data.data.places); - set(data?.data.places); + return data?.data.places; } catch (error) { console.log(error); } } -export async function keywordSearch( - keyword: string, - location: string, - sort: string, - set: Dispatch>, -) { +export async function keywordSearch(keyword: string, location: string, sort: string, page: number, size = 20) { try { const searchLocation = translateLocation(location); const categoryCode = translateCategoryCode(keyword); + const params: PramsType = { + page: page, + size: size, + sort: translateSort(sort), + categoryCode: categoryCode, + }; + if (searchLocation.areaCode !== 0) { + params.areaCode = searchLocation.areaCode; + if (searchLocation.sigunguCode !== 0) { + params.sigunguCode = searchLocation.sigunguCode; + } + } + const fetchData = await axios.get('/api/places/search', { - params: { - page: 0, - size: 20, - areaCode: searchLocation.areaCode, - sigunguCode: searchLocation.sigunguCode, - sort: translateSort(sort), - categoryCode: categoryCode, - }, + params: params, }); const data: DataType = fetchData.data; - set(data?.data.places); + return data?.data.places; } catch (error) { console.log(error); } } export async function getPopularItem( - apiURL: string, type: number, set: Dispatch>, ) { try { - const fetchData = await axios.get(`${apiURL}`, { + const fetchData = await axios.get('/api/places/popular', { params: { size: 10, placeTypeId: type, diff --git a/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx b/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx index 97ab009b..bf78cb9d 100644 --- a/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx +++ b/src/components/Detail/Navigation/MeatballBottomSlide/MeatballBottomSlide.tsx @@ -2,10 +2,13 @@ import {BiTask} from 'react-icons/bi'; import {CiEdit} from 'react-icons/ci'; import {FaRegHeart} from 'react-icons/fa'; import {IoShareSocialOutline} from 'react-icons/io5'; +import {useLocation} from 'react-router-dom'; import {useRecoilState, useSetRecoilState} from 'recoil'; import styles from './MeatballBottomSlide.module.scss'; +import {useDeleteWishes, usePostWishes} from '@/hooks/Detail/useWish'; + import CustomToast from '@/components/CustomToast/CustomToast'; import CloseIcon from '@/assets/close.svg?react'; @@ -16,8 +19,6 @@ import RegistrationSlide from '../../BottomFixedBtn/RegistrationSlide/Registrati import ReviewBottomSlide from '../../Contents/ReviewBottomSlide/ReviewBottomSlide'; import {NavigationMeatballProps} from '@/types/detail'; -import {useDeleteWishes, usePostWishes} from '@/hooks/Detail/useWish'; -import {useLocation} from 'react-router-dom'; const MeatballBottomSlide = ({onBottomSlideOpen, onClose, id, contentTypeId, title}: NavigationMeatballProps) => { const [isWish, setIsWish] = useRecoilState(IsHeartValued); @@ -41,8 +42,8 @@ const MeatballBottomSlide = ({onBottomSlideOpen, onClose, id, contentTypeId, tit }; const showToast = CustomToast(); - const postWishes = usePostWishes(); - const deleteWishes = useDeleteWishes(); + const postWishes = usePostWishes(id); + const deleteWishes = useDeleteWishes(id); const handleHeartClick = () => { if (isLogin) { diff --git a/src/components/Home/RecommendedItemList/RecommendedItemList.tsx b/src/components/Home/RecommendedItemList/RecommendedItemList.tsx index 8b5fdc79..2dff7753 100644 --- a/src/components/Home/RecommendedItemList/RecommendedItemList.tsx +++ b/src/components/Home/RecommendedItemList/RecommendedItemList.tsx @@ -125,16 +125,6 @@ function RecommendedItemList({apiNum}: PropsType) { }, ], [ - { - id: 3065360, - contentTypeId: 28, - title: '부산 요트투어 요트야', - thumbnail: 'https://d2ur7st6jjikze.cloudfront.net/offer_photos/119386/832573_large_1686709791.jpg?1686709791', - areaCode: 6, - sigunguCode: 16, - category: '관광', - rating: '5.0', - }, { id: 2774550, contentTypeId: 28, @@ -229,6 +219,16 @@ function RecommendedItemList({apiNum}: PropsType) { category: '관광', rating: '4.5', }, + { + id: 3065360, + contentTypeId: 28, + title: '부산 요트투어 요트야', + thumbnail: 'https://d2ur7st6jjikze.cloudfront.net/offer_photos/119386/832573_large_1686709791.jpg?1686709791', + areaCode: 6, + sigunguCode: 16, + category: '관광', + rating: '5.0', + }, ], ]; diff --git a/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx b/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx index 3aad2b76..164b180d 100644 --- a/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx +++ b/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx @@ -61,7 +61,7 @@ function CardHaveVote({data}: PropsType) { {data.title}

- + - diff --git a/src/components/Vote/VoteContent/VoteContent.module.scss b/src/components/Vote/VoteContent/VoteContent.module.scss index 5541e0b4..7eb59396 100644 --- a/src/components/Vote/VoteContent/VoteContent.module.scss +++ b/src/components/Vote/VoteContent/VoteContent.module.scss @@ -2,35 +2,39 @@ .container { width: 100%; - padding: 20px; display: flex; flex-direction: column; flex-grow: 1; - &__stateBar { - margin-bottom: 15px; - display: flex; - justify-content: space-between; - align-items: center; - - &__state { - @include typography(captionSmall); + .paddingBox { + padding: 16px 20px 0 20px; + .stateBar { + margin-bottom: 15px; display: flex; - gap: 4px; + justify-content: space-between; align-items: center; - & > svg { - font-size: 1.5rem; + &__state { + @include typography(captionSmall); + + display: flex; + gap: 4px; + align-items: center; + + & > svg { + font-size: 1.5rem; + } } - } - &__addCandidate { - @include typography(captionSmall); - color: $neutral900; - &:disabled { - color: $neutral300; + &__addCandidate { + @include typography(captionSmall); + color: $neutral900; + + &:disabled { + color: $neutral300; + } } } } diff --git a/src/components/Vote/VoteContent/VoteContent.tsx b/src/components/Vote/VoteContent/VoteContent.tsx index 18340873..47650b97 100644 --- a/src/components/Vote/VoteContent/VoteContent.tsx +++ b/src/components/Vote/VoteContent/VoteContent.tsx @@ -36,33 +36,41 @@ const VoteContent = ({onBottomSlideOpen, data, isZeroCandidates, showResults}: V return (
-
-
- {data.voteStatus === '결정완료' ? : } - {data.voteStatus} +
+
+
+ {data.voteStatus === '결정완료' ? : } + {data.voteStatus} +
+ + {!showResults && ( + + )}
- {!showResults && ( - - )} +
- - - {!isZeroCandidates && } + {!isZeroCandidates && ( + + )} {isCandidateSelecting && }
diff --git a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendItem/VoteRecommendItem.module.scss b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendItem/VoteRecommendItem.module.scss index 80591e5b..887a1196 100644 --- a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendItem/VoteRecommendItem.module.scss +++ b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendItem/VoteRecommendItem.module.scss @@ -10,12 +10,15 @@ width: 14.4rem; height: 14.4rem; + border-radius: 16px; + margin-bottom: 4px; + + background-color: $neutral200; & > img { width: 100%; height: 100%; object-fit: cover; - border-radius: 16px; } } diff --git a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendItem/VoteRecommendItem.tsx b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendItem/VoteRecommendItem.tsx index e42e8d26..8db5866e 100644 --- a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendItem/VoteRecommendItem.tsx +++ b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendItem/VoteRecommendItem.tsx @@ -1,17 +1,32 @@ import {AiOutlineDownload} from 'react-icons/ai'; import {BiTask} from 'react-icons/bi'; -import {FaStar} from 'react-icons/fa'; import {Link} from 'react-router-dom'; import styles from './VoteRecommendItem.module.scss'; -const VoteRecommendItem = ({state}: {state: string}) => { +import nullImg from '@/assets/homeIcons/search/nullImg.svg'; +import areas from '@/utils/areas.json'; +import titleCaseChange from '@/utils/titleCaseChange'; +import {translateCategoryToStr} from '@/utils/translateSearchData'; + +import {SearchItemType} from '@/types/home'; + +interface PropsType { + state: string; + data: SearchItemType; +} + +const VoteRecommendItem = ({state, data}: PropsType) => { + const location = areas.filter((area) => area.areaCode === data.location.areaCode)[0].name; + const category = translateCategoryToStr(data.contentTypeId); + const title = titleCaseChange(data.title); + const imgSrc = data.thumbnail ? data.thumbnail : nullImg; // 이미지, 정보 텍스트 -> 장소상페 이동 return (
- + {`${data.title}의 {state === '결정완료' ? ( @@ -28,17 +43,20 @@ const VoteRecommendItem = ({state}: {state: string}) => {
-

카페 리젠트마린

-

카페·제주

+

{title}

+

+ {category}·{location} +

-
+ {/* 해당 인기 장소 불러오는 api에 리뷰 정보가 없습니다 */} + {/*

4.4

-
+
*/}
); diff --git a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss index 2cf284eb..96634bbf 100644 --- a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss +++ b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss @@ -4,11 +4,25 @@ margin-bottom: 70px; position: relative; + -ms-overflow-style: none; + scrollbar-width: none; + ::-webkit-scrollbar { + display: none; + } + &__title { + padding-left: 20px; @include typography(titleLarge); margin-bottom: 15px; } + .slide { + @include slide_button_container; + &__list { + @include slide_list_container; + gap: 16px; + } + } } .dimmedOverlay { diff --git a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx index dbbb66a1..c6e5de5a 100644 --- a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx +++ b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx @@ -1,21 +1,71 @@ -import {Navigation} from 'swiper/modules'; -import {Swiper, SwiperSlide} from 'swiper/react'; +import {useEffect, useState} from 'react'; import 'swiper/scss'; import 'swiper/scss/navigation'; import styles from './VoteRecommendList.module.scss'; +import useComponentSize from '@/hooks/useComponetSize'; + +import SlideButton from '@/components/SlideButton/SlideButton'; + +import {keywordSearch} from '@/api/search'; +import {translateCategoryName} from '@/utils/translateSearchData'; + import VoteRecommendItem from './VoteRecommendItem/VoteRecommendItem'; +import {SearchItemType} from '@/types/home'; + // 후보지&여행지 X -> 상품 추천 없음 +interface PropsType { + state: string; + isCandidateSelecting: boolean; + categoryCode: string; +} + +const VoteRecommendList = ({state, isCandidateSelecting, categoryCode}: PropsType) => { + const [data, setData] = useState(); + const [slideLocation, setSlideLocation] = useState(0); + const [componentRef, size] = useComponentSize(); + const category = translateCategoryName(categoryCode); + // 저 keyword자리에 A5480380 이런 것을 넣고 categorycode를 keywordSearch의 '카페'자리에 넣어주시고 title에 이런 ${category}는 어때요? 해주시면 완성되는 로직... + // const categoryCode = translateCategoryName(keyword); + + async function getData() { + const fetchData = await keywordSearch(category, '전국', '인기순', 0, 5); + setData(fetchData); + } + + useEffect(() => { + getData(); + }, []); -const VoteRecommendList = ({state, isCandidateSelecting}: {state: string; isCandidateSelecting: boolean}) => { return (
{isCandidateSelecting &&
} -

이런 카페는 어때요?

- - +

이런 {category} 어때요?

+
+ {data && ( + + )} +
+ {data && data.map((data) => )} +
+
+ {/* @@ -28,7 +78,7 @@ const VoteRecommendList = ({state, isCandidateSelecting}: {state: string; isCand - +
*/}
); }; diff --git a/src/components/WishBtn/WishBtn.tsx b/src/components/WishBtn/WishBtn.tsx index 474fda45..d4a87462 100644 --- a/src/components/WishBtn/WishBtn.tsx +++ b/src/components/WishBtn/WishBtn.tsx @@ -36,26 +36,27 @@ function WishBtn({placeId, contentTypeId, size = '2.4rem', className = ''}: Wish useEffect(() => { if (isLogin) { - if (typeof wish === 'boolean') { - setIsWish(wish); + if (typeof wish.data === 'boolean') { + setIsWish(wish.data); } } }, []); - const postWishes = usePostWishes(); - const deleteWishes = useDeleteWishes(); + const postWishes = usePostWishes(placeId); + const deleteWishes = useDeleteWishes(placeId); const debounce = useDebounceBoolean(isWish, 1000); const handleWishClick = () => { if (isLogin) { if (!isWish) { - postWishes.mutate({placeId: placeId, contentTypeId: contentTypeId}); + // 여기서 한 번 요청하고 밑에 디바운스에서 또 요청해서 주석처리 해놨습니다! + // postWishes.mutate({placeId: placeId, contentTypeId: contentTypeId}); setIsWish(true); showToast('찜 목록에 저장되었습니다.'); } else { - deleteWishes.mutate(placeId); + // deleteWishes.mutate(placeId); showToast('찜 목록에서 제거되었습니다.'); setIsWish(false); diff --git a/src/hooks/Detail/useWish.ts b/src/hooks/Detail/useWish.ts index 7985207d..b8acce59 100644 --- a/src/hooks/Detail/useWish.ts +++ b/src/hooks/Detail/useWish.ts @@ -6,16 +6,17 @@ import {useCustomMutation} from '../Votes/vote'; export const useGetIsWish = (id: number, enabled: boolean) => { return useQuery({ - queryKey: ['isWish', id], + // 키 동적으로 바뀌게 바꾸어놨습니다! + queryKey: [`wish ${id}`], enabled, queryFn: () => getIsWish(id), }); }; -export const usePostWishes = () => { - return useCustomMutation(postWishes, ['isWish']); +export const usePostWishes = (id: number) => { + return useCustomMutation(postWishes, [`wish ${id}`]); }; -export const useDeleteWishes = () => { - return useCustomMutation(deleteWishes, ['isWish']); +export const useDeleteWishes = (id: number) => { + return useCustomMutation(deleteWishes, [`wish ${id}`]); }; diff --git a/src/pages/Vote/Vote.module.scss b/src/pages/Vote/Vote.module.scss index 21b79120..6dc72b52 100644 --- a/src/pages/Vote/Vote.module.scss +++ b/src/pages/Vote/Vote.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { min-width: 36rem; diff --git a/src/utils/translateSearchData.ts b/src/utils/translateSearchData.ts index b139b712..088377e5 100644 --- a/src/utils/translateSearchData.ts +++ b/src/utils/translateSearchData.ts @@ -96,3 +96,8 @@ export function translateCategoryCode(name: string) { const categoryCode = categoryData.filter((data) => data.name === name)[0]; return categoryCode.code; } + +export function translateCategoryName(name: string) { + const categoryCode = categoryData.filter((data) => data.code === name)[0]; + return categoryCode.name; +}