diff --git a/src/App.tsx b/src/App.tsx index 0fa8770b..fbc27d07 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,11 @@ import MainRouter from './routes/MainRouter/MainRouter'; window.Kakao.init(import.meta.env.VITE_KAKAO_KEY); const queryClient = new QueryClient(); +queryClient.setDefaultOptions({ + queries: { + refetchOnWindowFocus: false, + }, +}); function App() { return ( diff --git a/src/api/detail.ts b/src/api/detail.ts index 28682211..f890d8ea 100644 --- a/src/api/detail.ts +++ b/src/api/detail.ts @@ -1,6 +1,7 @@ -import {MySpaces, PlacesNearby, PostReview, Reviews, ReviewsRating, Wishes, placeInfoData} from '@/types/detail'; import axios from 'axios'; +import {MySpaces, placeInfoData, PlacesNearby, PostReview, Reviews, ReviewsRating, Wishes} from '@/types/detail'; + // --------------------------- GET --------------------------- export const getPlaceInfo = async (id: number, typeId: number): Promise => { @@ -27,13 +28,10 @@ export const getReviews = async (id: number, typeId: number, title: string): Pro return response.data; }; -export const getIsWish = async (id: number, setIsWish: React.Dispatch>) => { +export const getIsWish = async (id: number) => { const response = await axios.get(`/api/wishes/${id}`, { withCredentials: true, }); - - setIsWish(response.data.data); - console.log(response.data); return response.data; diff --git a/src/api/wishes.ts b/src/api/wishes.ts new file mode 100644 index 00000000..59d13ce3 --- /dev/null +++ b/src/api/wishes.ts @@ -0,0 +1,15 @@ +import axios from 'axios'; +import {Dispatch} from 'react'; + +import {DataType} from '@/types/home'; +import {Wishes} from '@/types/wish'; + +export async function getUserWishes(set: Dispatch) { + try { + const fetchData = await axios.get('/api/members/my-places'); + const data: DataType = fetchData.data; + set(data.data); + } catch (error) { + console.log(error); + } +} diff --git a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx index abf482d2..84501ce8 100644 --- a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx +++ b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx @@ -23,7 +23,6 @@ const SelectButton = ({data}: Propstype) => { e.preventDefault(); setIsClicked((prev) => !prev); toggleItemInNewArray(data); - console.log(data); }; useEffect(() => { diff --git a/src/components/GlobalNavigationBar/GlobalNavigationBar.tsx b/src/components/GlobalNavigationBar/GlobalNavigationBar.tsx index 9890288f..511232bb 100644 --- a/src/components/GlobalNavigationBar/GlobalNavigationBar.tsx +++ b/src/components/GlobalNavigationBar/GlobalNavigationBar.tsx @@ -14,11 +14,13 @@ function GlobalNavigationBar() { - + + - - + + + diff --git a/src/components/Home/VoteAtHome/VoteAtHome.tsx b/src/components/Home/VoteAtHome/VoteAtHome.tsx index 886416ad..9319d4ac 100644 --- a/src/components/Home/VoteAtHome/VoteAtHome.tsx +++ b/src/components/Home/VoteAtHome/VoteAtHome.tsx @@ -2,6 +2,8 @@ import {useEffect, useState} from 'react'; import styles from './VoteAtHome.module.scss'; +import {useGetMyInfo} from '@/hooks/User/useUser'; + import {getHomeVote} from '@/api/home'; import CardHaveVote from './VoteCard/CardHaveVote/CardHaveVote'; @@ -11,6 +13,7 @@ import {Vote} from '@/types/home'; function VoteAtHome() { const [data, setData] = useState(); + const userData = useGetMyInfo(true).data?.data.nickname; useEffect(() => { getHomeVote(setData); @@ -20,7 +23,7 @@ function VoteAtHome() {
{data && data.voteResponse.length > 0 ? (

- 길동님 + {userData} 님
진행 중인 투표가 있어요!

diff --git a/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx b/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx index bfe9aed3..19de378d 100644 --- a/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx +++ b/src/components/Home/VoteAtHome/VoteCard/CardHaveVote/CardHaveVote.tsx @@ -39,6 +39,7 @@ function CardHaveVote({data}: PropsType) { > {data && data.voteResponse.map((data) => { + const voteTripTitle = data.spaceInfo.title ? data.spaceInfo.title : '여행지 미정'; const date = setSpaceDate_DOW(data.spaceInfo.startDate, data.spaceInfo.endDate); return ( @@ -46,7 +47,7 @@ function CardHaveVote({data}: PropsType) {

- {data.spaceInfo.title} + {voteTripTitle} {date}

diff --git a/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.tsx b/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.tsx index 9d99fce0..465db15f 100644 --- a/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.tsx +++ b/src/components/SearchFromHome/SearchList/DateFilter/DateFilter.tsx @@ -40,6 +40,7 @@ function DateFilter({forSearch}: PropsType) { onClick={() => { selectSort(data); }} + key={data} > {data} diff --git a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.module.scss b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.module.scss index eaa5b70b..62f294ce 100644 --- a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.module.scss +++ b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.module.scss @@ -1,6 +1,8 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { + position: relative; + min-width: 33.5rem; min-height: 12rem; @@ -15,9 +17,11 @@ background-color: $neutral0; img { - width: 9.6rem; + min-width: 9.6rem; height: 9.6rem; + border-radius: 8px; + transition: 0.3s all linear; } @@ -38,4 +42,9 @@ @include typography(captionSmall); } } + .wishBtn { + position: absolute; + top: 16px; + right: 16px; + } } diff --git a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.tsx b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.tsx index 38e163c8..c61dbce3 100644 --- a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.tsx +++ b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItem/MapItem.tsx @@ -2,6 +2,8 @@ import {Link} from 'react-router-dom'; import styles from './MapItem.module.scss'; +import WishBtn from '@/components/WishBtn/WishBtn'; + import areas from '@/utils/areas.json'; import {translateCategoryToStr} from '@/utils/translateSearchData'; @@ -25,6 +27,7 @@ function MapItem({data, categoryChange}: PropsType) { {category}·{location}

+ ); } diff --git a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItems.module.scss b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItems.module.scss index be275f5e..edd784f1 100644 --- a/src/components/SearchFromHome/SearchList/Map/MapItems/MapItems.module.scss +++ b/src/components/SearchFromHome/SearchList/Map/MapItems/MapItems.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { position: relative; diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx index 2adc36c6..98c2adc5 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tab/Tab.tsx @@ -1,3 +1,4 @@ +import {useEffect, useState} from 'react'; import {useNavigate} from 'react-router-dom'; import styles from './Tab.module.scss'; @@ -5,24 +6,39 @@ import styles from './Tab.module.scss'; import {translateCategoryToNum, translateCategoryToStr} from '@/utils/translateSearchData'; import {ForSearchType} from '@/types/home'; +import {WishFilterType} from '@/types/wish'; interface PropsType { - forSearch: ForSearchType; + forSearch: ForSearchType | undefined; + wishFilter: WishFilterType | undefined; thisCategory: string; setCategoryChange: React.Dispatch>; } -function Tab({forSearch, thisCategory, setCategoryChange}: PropsType) { +function Tab({forSearch, thisCategory, wishFilter, setCategoryChange}: PropsType) { + const [isSelect, setIsSelect] = useState(false); const navigate = useNavigate(); + useEffect(() => { + if (forSearch) { + setIsSelect(translateCategoryToStr(forSearch.category) === thisCategory); + } else if (wishFilter) { + setIsSelect(translateCategoryToStr(wishFilter.category) === thisCategory); + } + }, [forSearch, wishFilter]); + function handleCategory(key: number) { setCategoryChange(true); setTimeout(() => { setCategoryChange(false); }, 150); - navigate( - `/search?keyword=${forSearch.keyword}&category=${key}&map=${forSearch.map}&location=${forSearch.location}&sort=${forSearch.sort}&hot=${forSearch.hot}&placeID=${forSearch.placeID}&tripDate=${forSearch.tripDate}`, - ); + if (forSearch) { + navigate( + `/search?keyword=${forSearch.keyword}&category=${key}&map=${forSearch.map}&location=${forSearch.location}&sort=${forSearch.sort}&hot=${forSearch.hot}&placeID=${forSearch.placeID}&tripDate=${forSearch.tripDate}`, + ); + } else if (wishFilter) { + navigate(`/wishes?category=${key}&placeID=${wishFilter.placeID}&tripDate=${wishFilter.tripDate}`); + } } return ( @@ -30,8 +46,8 @@ function Tab({forSearch, thisCategory, setCategoryChange}: PropsType) { className={styles.container} id={thisCategory} style={{ - color: translateCategoryToStr(forSearch.category) === thisCategory ? '#1d2433' : '#cdcfd0', - borderBottom: translateCategoryToStr(forSearch.category) === thisCategory ? '2px solid #1d2433' : 'none', + color: isSelect ? '#1d2433' : '#cdcfd0', + borderBottom: isSelect ? '2px solid #1d2433' : 'none', }} onClick={() => { handleCategory(translateCategoryToNum(thisCategory)); diff --git a/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx b/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx index b9c0942b..69624969 100644 --- a/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx +++ b/src/components/SearchFromHome/SearchList/Tabs/Tabs.tsx @@ -11,14 +11,16 @@ import {translateCategoryToStr} from '@/utils/translateSearchData'; import Tab from './Tab/Tab'; import {ForSearchType, SearchItemType} from '@/types/home'; +import {WishFilterType} from '@/types/wish'; interface PropsType { data: SearchItemType[] | undefined; - forSearch: ForSearchType; + forSearch?: ForSearchType | undefined; + wishFilter?: WishFilterType | undefined; setCategoryChange: React.Dispatch>; } -function Tabs({data, forSearch, setCategoryChange}: PropsType) { +function Tabs({data, forSearch = undefined, wishFilter = undefined, setCategoryChange}: PropsType) { const [category, setCategory] = useState(); const [slideLocation, setSlideLocation] = useState(0); const [componentRef, size] = useComponentSize(); @@ -28,7 +30,9 @@ function Tabs({data, forSearch, setCategoryChange}: PropsType) { const dataCategory: string[] = []; data.map((data) => { const categoryData = translateCategoryToStr(data.contentTypeId); - dataCategory.push(categoryData); + if (categoryData !== '전체') { + dataCategory.push(categoryData); + } }); const set = new Set(dataCategory); const push = ['전체', ...set]; @@ -61,6 +65,7 @@ function Tabs({data, forSearch, setCategoryChange}: PropsType) { category.map((thisCategory) => ( {isWish ? ( - + ) => { + e.stopPropagation(); + e.preventDefault(); + handleWishClick(); + }} + className={className} + /> ) : ( - + ) => { + e.stopPropagation(); + e.preventDefault(); + handleWishClick(); + }} + className={className} + /> )} ); diff --git a/src/components/WishItem/WishItem.module.scss b/src/components/WishItem/WishItem.module.scss new file mode 100644 index 00000000..24dee0d6 --- /dev/null +++ b/src/components/WishItem/WishItem.module.scss @@ -0,0 +1,51 @@ +@use '@/sass' as *; + +.container { + width: 100%; + max-height: 70px; + + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + + padding: 16px 0; + + .itemBox { + max-height: 70px; + + display: flex; + gap: 12px; + + img { + width: 4rem; + height: 4rem; + + border-radius: 0.8rem; + + opacity: 1; + + transition: 0.3s all linear; + + background-color: $neutral200; + } + + .text { + display: flex; + flex-direction: column; + + opacity: 1; + + transition: 0.3s all linear; + + .title { + @include typography(titleSmall); + } + .info { + color: $neutral400; + + @include typography(captionSmall); + } + } + } +} diff --git a/src/components/WishItem/WishItem.tsx b/src/components/WishItem/WishItem.tsx new file mode 100644 index 00000000..16884627 --- /dev/null +++ b/src/components/WishItem/WishItem.tsx @@ -0,0 +1,57 @@ +import {Link} from 'react-router-dom'; + +import styles from './WishItem.module.scss'; + +import SelectButton from '@/components/ButtonsInAddingCandidate/SelectButton/SelectButton'; +import WishBtn from '@/components/WishBtn/WishBtn'; + +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 WishFilterType { + category: number; + placeID: string; + tripDate: string; +} + +interface PropsType { + filter: WishFilterType; + data: SearchItemType; + categoryChange: boolean; +} + +function WishItem({filter, data, categoryChange}: PropsType) { + const title = titleCaseChange(data.title); + const location = areas.filter((area) => area.areaCode === data.location.areaCode)[0].name; + const category = translateCategoryToStr(data.contentTypeId); + const imgSrc = data.thumbnail ? data.thumbnail : nullImg; + + return ( + +
+ {`${data.title}의 +

+ {title} + + {category}·{location} + +

+
+ {filter.placeID === 'undefined' ? ( + + ) : ( + + )} + + ); +} + +export default WishItem; diff --git a/src/hooks/Detail/useWish.ts b/src/hooks/Detail/useWish.ts index b8f17fb0..feacf302 100644 --- a/src/hooks/Detail/useWish.ts +++ b/src/hooks/Detail/useWish.ts @@ -1,11 +1,13 @@ -import {deleteWishes, getIsWish, postWishes} from '@/api/detail'; import {useSuspenseQuery} from '@tanstack/react-query'; + +import {deleteWishes, getIsWish, postWishes} from '@/api/detail'; + import {useCustomMutation} from '../Votes/vote'; -export const useGetIsWish = (id: number, setIsWish: React.Dispatch>) => { +export const useGetIsWish = (id: number) => { return useSuspenseQuery({ queryKey: ['isWish', id], - queryFn: () => getIsWish(id, setIsWish), + queryFn: () => getIsWish(id), }); }; diff --git a/src/pages/Wishes/Wishes.module.scss b/src/pages/Wishes/Wishes.module.scss new file mode 100644 index 00000000..c9625f09 --- /dev/null +++ b/src/pages/Wishes/Wishes.module.scss @@ -0,0 +1,39 @@ +@use '@/sass' as *; + +.container { + width: 100%; + height: calc(100% - 56px); + + .header { + position: relative; + + width: 100%; + height: 6rem; + + @include typography(subTitle); + span { + position: absolute; + top: 50%; + left: 50%; + + transform: translate(-50%, -50%); + } + } + .slide { + padding: 24px 20px 0 20px; + } + .nullBox { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + + margin-top: 83px; + + color: $neutral300; + + transition: 1s all; + + @include typography(subTitle); + } +} diff --git a/src/pages/Wishes/Wishes.tsx b/src/pages/Wishes/Wishes.tsx new file mode 100644 index 00000000..86a178e7 --- /dev/null +++ b/src/pages/Wishes/Wishes.tsx @@ -0,0 +1,90 @@ +import {useEffect, useState} from 'react'; +import {useSearchParams} from 'react-router-dom'; + +import styles from './Wishes.module.scss'; + +import AddToCandidateButton from '@/components/ButtonsInAddingCandidate/AddToCandidateButton/AddToCandidateButton'; +import Tabs from '@/components/SearchFromHome/SearchList/Tabs/Tabs'; + +import {getUserWishes} from '@/api/wishes'; +import SearchNull from '@/assets/homeIcons/search/searchNull.svg?react'; + +import WishItem from '../../components/WishItem/WishItem'; + +import {SearchItemType} from '@/types/home'; +import {Wishes} from '@/types/wish'; + +function Wishes() { + const [data, setData] = useState(); + const [filterData, setFilterData] = useState(); + const [categoryChange, setCategoryChange] = useState(false); + const [searchParams] = useSearchParams(); + const [filter, setFilter] = useState({ + category: 0, + placeID: 'undefined', + tripDate: 'undefinde', + }); + + useEffect(() => { + const querystring = { + placeID: searchParams.get('placeID'), + tripDate: searchParams.get('tripDate'), + }; + if (querystring.placeID && !querystring.tripDate) { + setFilter({ + category: filter.category, + placeID: querystring.placeID, + tripDate: filter.tripDate, + }); + } + if (querystring.placeID && querystring.tripDate) { + setFilter({ + category: filter.category, + placeID: querystring.placeID, + tripDate: querystring.tripDate, + }); + } + }, [searchParams]); + + useEffect(() => { + getUserWishes(setData); + }, []); + + useEffect(() => { + if (data) { + if (filter.category !== 0) { + let filterData: SearchItemType[]; + if (filter.category === 14) { + filterData = data.places.filter((data) => data.contentTypeId === 14 || data.contentTypeId === 15); + } else { + filterData = data.places.filter((data) => data.contentTypeId === filter.category); + } + setFilterData(filterData); + } else { + setFilterData(data.places); + } + } + }, [data, filter.category]); + + return ( +
+

+ 찜 목록 +

+ {data && } +
    + {filterData && filterData?.length > 0 ? ( + filterData.map((data) => ) + ) : ( +
    + + 찜 목록이 비었습니다. +
    + )} +
+ {filter.placeID !== 'undefined' && } +
+ ); +} + +export default Wishes; diff --git a/src/routes/MainRouter/MainRouter.tsx b/src/routes/MainRouter/MainRouter.tsx index 8563372e..434d5ddb 100644 --- a/src/routes/MainRouter/MainRouter.tsx +++ b/src/routes/MainRouter/MainRouter.tsx @@ -26,6 +26,7 @@ import User from '@/pages/User/User'; import UserPrivacy from '@/pages/User/UserPrivacy/UserPrivacy'; import Vote from '@/pages/Vote/Vote'; import VoteMemo from '@/pages/Vote/VoteMemo/VoteMemo'; +import Wishes from '@/pages/Wishes/Wishes'; import Dashboard from '@/routes/Dashboard/Dashboard'; import {getTitle} from '@/utils/getTitle'; @@ -46,7 +47,7 @@ function MainRouter() { } /> } /> } /> - } /> + } /> } /> } /> } /> @@ -69,6 +70,7 @@ function MainRouter() { } /> } /> } /> + } /> ); diff --git a/src/types/wish.ts b/src/types/wish.ts new file mode 100644 index 00000000..a18aff37 --- /dev/null +++ b/src/types/wish.ts @@ -0,0 +1,26 @@ +import {SearchItemType} from './home'; + +export interface WishesData { + id: number; + area: string; + category: string; + title: string; + thumbnail: string; + contentTypeId: number; +} + +export interface Wishes { + places: SearchItemType[]; + pageNumber: number; + pageSize: number; + totalPages: number; + totalResult: number; + first: boolean; + last: boolean; +} + +export interface WishFilterType { + category: number; + placeID: string; + tripDate: string; +}