diff --git a/package-lock.json b/package-lock.json index 27c09be6..d2c4193e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "react-mobile-datepicker": "^4.0.2", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", + "recoil-persist": "^5.1.0", "swiper": "^11.0.5" }, "devDependencies": { @@ -10374,6 +10375,14 @@ } } }, + "node_modules/recoil-persist": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/recoil-persist/-/recoil-persist-5.1.0.tgz", + "integrity": "sha512-sew4k3uBVJjRWKCSFuBw07Y1p1pBOb0UxLJPxn4G2bX/9xNj+r2xlqYy/BRfyofR/ANfqBU04MIvulppU4ZC0w==", + "peerDependencies": { + "recoil": "^0.7.2" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", diff --git a/package.json b/package.json index f6b7c0f3..f6474bc7 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-mobile-datepicker": "^4.0.2", "react-router-dom": "^6.21.1", "recoil": "^0.7.7", + "recoil-persist": "^5.1.0", "swiper": "^11.0.5" }, "devDependencies": { diff --git a/src/api/vote.ts b/src/api/vote.ts index 71091d28..6583339a 100644 --- a/src/api/vote.ts +++ b/src/api/vote.ts @@ -19,7 +19,7 @@ export const getVoteInfo = async (voteId: number): Promise => { //보트 리스트 export const getVoteListInfo = async (spaceId: number): Promise => { - const response = await axios.get(`/api/votes/${spaceId}`); + const response = await axios.get(`/api/votes`, {params: {spaceId, voteStatusOption: 'ALL'}}); return response.data; }; @@ -28,7 +28,7 @@ export const getVoteListInfo = async (spaceId: number): Promise /* ----------------------------------- P O S T ---------------------------------- */ //vote 추가 -export const PostNewVote = async ({spaceId, title}: PostVoteTitleProps) => { +export const postNewVote = async ({spaceId, title}: PostVoteTitleProps) => { try { const response = await axios.post('/api/votes', {spaceId, title}); console.log('axios 포스트 성공', response); diff --git a/src/components/BottomSlide/BottomSlide.tsx b/src/components/BottomSlide/BottomSlide.tsx index 5abad792..02566aac 100644 --- a/src/components/BottomSlide/BottomSlide.tsx +++ b/src/components/BottomSlide/BottomSlide.tsx @@ -1,33 +1,33 @@ -import { Slide } from "@chakra-ui/react"; -import { useRef } from "react"; +import {Slide} from '@chakra-ui/react'; +import {useRef} from 'react'; -import styles from "./BottomSlide.module.scss"; +import styles from './BottomSlide.module.scss'; -import useOnClickOutside from "@/hooks/useOnClickOutside"; +import useOnClickOutside from '@/hooks/useOnClickOutside'; -import CloseIcon from "@/assets/close.svg?react"; +import CloseIcon from '@/assets/close.svg?react'; -import { BottomSlideProps } from "../../types/bottomSlide"; +import {BottomSlideProps} from '../../types/bottomSlide'; -function BottomSlide({ isOpen, onClose, children }: BottomSlideProps) { +function BottomSlide({isOpen, onClose, children}: BottomSlideProps) { const containerStyle = { - display: isOpen ? "block" : "none", + display: isOpen ? 'block' : 'none', + }; + + const closeModal = () => { + onClose(); + document.body.style.overflow = 'visible'; }; const slideRef = useRef(null); - useOnClickOutside(slideRef, onClose); + useOnClickOutside(slideRef, closeModal); return (
- +
-
diff --git a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx index fd7d18a0..061421b6 100644 --- a/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx +++ b/src/components/ButtonsInAddingCandidate/SelectButton/SelectButton.tsx @@ -1,20 +1,34 @@ import {useState} from 'react'; +import {useRecoilValue} from 'recoil'; import styles from './SelectButton.module.scss'; -import useGetSelectedCandidates from '@/hooks/useGetSelectedCandidates'; +import useGetSelectedArray from '@/hooks/useGetSelectedArray'; + +import {selectedPlaceState} from '@/recoil/vote/selectPlace'; + +// //"선택된 장소 객체" +const placeInfo = { + placeId: 23, + placeName: '안녕호텔', + category: '호텔', + location: '서울', + placeImageURL: 'https://img-cf.kurly.com/shop/data/goodsview/20210218/gv30000159355_1.jpg', + latlng: {lat: 33.450936, lng: 126.569477}, +}; + const SelectButton = () => { const [isClicked, setIsClicked] = useState(false); - const {addCandidateInSelectedList} = useGetSelectedCandidates(); + const selectedPlaces = useRecoilValue(selectedPlaceState); - //"선택된 장소의 ID" - const placeId = 22; + const {toggleItemInNewArray} = useGetSelectedArray(selectedPlaceState); const handleClick = () => { setIsClicked((prev) => !prev); - addCandidateInSelectedList(placeId); + toggleItemInNewArray(placeInfo); }; + console.log('선택한 배열', selectedPlaces); return ( +
+ ); +}; + +export default AddToJourney; diff --git a/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx b/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx index 59c78950..d1d0ea4a 100644 --- a/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx +++ b/src/components/Vote/VoteBottomSlideContent/VoteMeatball/VoteMeatball.tsx @@ -21,7 +21,7 @@ import CreateVoteModal from '../../CreateVoteModal/CreateVoteModal'; import {AlertModalProps, VoteMeatballProps} from '@/types/vote'; -const VoteMeatball = ({state, title, isZeroCandidates}: VoteMeatballProps) => { +const VoteMeatball = ({state, title, isZeroCandidates, allCandidatesNotVoted}: VoteMeatballProps) => { const {id: voteId} = useParams(); const setIsCreateModalOpen = useSetRecoilState(isCreateModalOpenState); const setIsBTOpen = useSetRecoilState(isBottomSlideOpenState); @@ -65,7 +65,7 @@ const VoteMeatball = ({state, title, isZeroCandidates}: VoteMeatballProps) => { ) : ( - diff --git a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.module.scss b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.module.scss index 0cc00386..3171b07f 100644 --- a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.module.scss +++ b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.module.scss @@ -69,6 +69,7 @@ // gap: 8px; &__name { + text-align: start; @include typography(titleSmall); color: $neutral900; } @@ -110,3 +111,9 @@ } } } + +.isCandidateSelecting { + * { + color: $neutral300; + } +} diff --git a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx index 2e6ace77..36cec270 100644 --- a/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx +++ b/src/components/Vote/VoteContent/CandidateCard/CandidateCard.tsx @@ -1,6 +1,5 @@ import {useState} from 'react'; import {FaRegStar, FaStar} from 'react-icons/fa'; -import {Link} from 'react-router-dom'; import {useRecoilValue} from 'recoil'; import styles from './CandidateCard.module.scss'; @@ -11,6 +10,7 @@ import ThirdIcon from '@/assets/voteIcons/rank_3.svg?react'; import AddDayIcon from '@/assets/voteIcons/vote_addDay.svg?react'; import {isCandidateSelectingState} from '@/recoil/vote/alertModal'; +import AddToJourney from '../../VoteBottomSlideContent/AddToJourney/AddToJourney'; import VotedUserList from '../../VoteBottomSlideContent/VotedUserList/VotedUserList'; import {CandidateCardProps} from '@/types/vote'; @@ -18,7 +18,8 @@ import {CandidateCardProps} from '@/types/vote'; const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapStyle}: CandidateCardProps) => { const [isVoted, setIsVoted] = useState(false); const isCandidateSelecting = useRecoilValue(isCandidateSelectingState); - const voteCounts = candidate.voteUserId.length; + + const placeInfo = candidate.placeInfo; const getRankClassName = (index: number) => { switch (index) { @@ -50,7 +51,7 @@ const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapS const RankIcon = showResults && getRankIcon(index); const voteStarIcon = () => { - if (isVoted) return ; + if (isVoted || candidate.amIVoted) return ; else if (isMapStyle) return ; else return ; }; @@ -65,7 +66,7 @@ const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapS return (
- {candidate.placeName} + {placeInfo.placeName} {RankIcon && (
@@ -74,14 +75,18 @@ const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapS
- - {candidate.placeName} {'>'} - +
- {candidate.category} + {placeInfo.category} {'ꞏ'} - {candidate.location} + {placeInfo.location}
{/* 일정 담기 @@ -90,15 +95,19 @@ const CandidateCard = ({onBottomSlideOpen, candidate, showResults, index, isMapS 있 : 바텀시트 -> 일정 추가api -> 시트close, 완료 토스트 */} - {showResults && ( - )}
-
diff --git a/src/components/Vote/VoteContent/CandidateList/CandidateList.module.scss b/src/components/Vote/VoteContent/CandidateList/CandidateList.module.scss index c5ff580d..342c668b 100644 --- a/src/components/Vote/VoteContent/CandidateList/CandidateList.module.scss +++ b/src/components/Vote/VoteContent/CandidateList/CandidateList.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { margin-bottom: 50px; @@ -18,7 +18,7 @@ &__memo { display: flex; align-items: center; - gap: 6px; + gap: 8px; margin-top: 16px; &__text { diff --git a/src/components/Vote/VoteContent/CandidateList/CandidateList.tsx b/src/components/Vote/VoteContent/CandidateList/CandidateList.tsx index 9a8f01dc..4f786258 100644 --- a/src/components/Vote/VoteContent/CandidateList/CandidateList.tsx +++ b/src/components/Vote/VoteContent/CandidateList/CandidateList.tsx @@ -1,8 +1,11 @@ import {Avatar, Checkbox} from '@chakra-ui/react'; +import {useSetRecoilState} from 'recoil'; import styles from './CandidateList.module.scss'; -import useGetSelectedCandidates from '@/hooks/useGetSelectedCandidates'; +import useGetSelectedSet from '@/hooks/useGetSelectedSet'; + +import {selectedCandidatesState} from '@/recoil/vote/candidateList'; import CandidateCard from '../CandidateCard/CandidateCard'; import VoteContentEmpty from '../VoteContentEmpty/VoteContentEmpty'; @@ -10,7 +13,8 @@ import VoteContentEmpty from '../VoteContentEmpty/VoteContentEmpty'; import {CandidateListProps} from '@/types/vote'; const CandidateList = ({candidates, onBottomSlideOpen, showResults, isCandidateSelecting}: CandidateListProps) => { - const {addCandidateInSelectedList} = useGetSelectedCandidates(); + const setSelectedCandidates = useSetRecoilState(selectedCandidatesState); + const {addItemInNewSet} = useGetSelectedSet(setSelectedCandidates); return (
@@ -24,7 +28,7 @@ const CandidateList = ({candidates, onBottomSlideOpen, showResults, isCandidateS fontSize='2rem' id={`${i}checkbox`} variant='candidateCheckbox' - onChange={() => addCandidateInSelectedList(candidate.id)} + onChange={() => addItemInNewSet(candidate.id)} /> )}
diff --git a/src/components/Vote/VoteContent/VoteContent.module.scss b/src/components/Vote/VoteContent/VoteContent.module.scss index 64459e1d..5541e0b4 100644 --- a/src/components/Vote/VoteContent/VoteContent.module.scss +++ b/src/components/Vote/VoteContent/VoteContent.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { width: 100%; @@ -26,8 +26,12 @@ } } &__addCandidate { - @include typography(button); + @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 6de5fc6f..4059ae90 100644 --- a/src/components/Vote/VoteContent/VoteContent.tsx +++ b/src/components/Vote/VoteContent/VoteContent.tsx @@ -14,7 +14,7 @@ import AddCandidate from '../VoteBottomSlideContent/AddCandidate/AddCandidate'; import {VoteContentProps} from '@/types/vote'; -const VoteContent = ({onBottomSlideOpen, data, showResults}: VoteContentProps) => { +const VoteContent = ({onBottomSlideOpen, data, isZeroCandidates, showResults}: VoteContentProps) => { const candidates = data.candidates; const isCandidateSelecting = useRecoilValue(isCandidateSelectingState); @@ -32,12 +32,15 @@ const VoteContent = ({onBottomSlideOpen, data, showResults}: VoteContentProps) = {data.voteStatus}
- + {!showResults && ( + + )}
- {/* 후보지&여행지 X -> 상품 추천 없음 */} - + + {!isZeroCandidates && } + {isCandidateSelecting && }
); diff --git a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss index 2780bd0a..2cf284eb 100644 --- a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss +++ b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.module.scss @@ -1,7 +1,8 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { margin-bottom: 70px; + position: relative; &__title { @include typography(titleLarge); @@ -9,3 +10,11 @@ margin-bottom: 15px; } } + +.dimmedOverlay { + position: absolute; + width: 100%; + height: 30rem; + background: #ffffff80; + z-index: 3; +} diff --git a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx index 0cf42f7c..dbbb66a1 100644 --- a/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx +++ b/src/components/Vote/VoteContent/VoteRecommendList/VoteRecommendList.tsx @@ -1,25 +1,21 @@ -import { Navigation } from "swiper/modules"; -import { Swiper, SwiperSlide } from "swiper/react"; -import "swiper/scss"; -import "swiper/scss/navigation"; +import {Navigation} from 'swiper/modules'; +import {Swiper, SwiperSlide} from 'swiper/react'; +import 'swiper/scss'; +import 'swiper/scss/navigation'; -import styles from "./VoteRecommendList.module.scss"; +import styles from './VoteRecommendList.module.scss'; -import VoteRecommendItem from "./VoteRecommendItem/VoteRecommendItem"; +import VoteRecommendItem from './VoteRecommendItem/VoteRecommendItem'; // 후보지&여행지 X -> 상품 추천 없음 -const VoteRecommendList = ({ state }: { state: string }) => { +const VoteRecommendList = ({state, isCandidateSelecting}: {state: string; isCandidateSelecting: boolean}) => { return (
+ {isCandidateSelecting &&
}

이런 카페는 어때요?

- + diff --git a/src/components/Vote/VoteHeader/VoteHeader.module.scss b/src/components/Vote/VoteHeader/VoteHeader.module.scss index 7134ae13..f8032a67 100644 --- a/src/components/Vote/VoteHeader/VoteHeader.module.scss +++ b/src/components/Vote/VoteHeader/VoteHeader.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { width: 100%; @@ -42,4 +42,8 @@ gap: 12px; font-size: 2.2rem; + + & > *:disabled { + color: $neutral300; + } } diff --git a/src/components/Vote/VoteHeader/VoteHeader.tsx b/src/components/Vote/VoteHeader/VoteHeader.tsx index bc7efeb7..52dd3a63 100644 --- a/src/components/Vote/VoteHeader/VoteHeader.tsx +++ b/src/components/Vote/VoteHeader/VoteHeader.tsx @@ -3,15 +3,19 @@ import {BsThreeDots} from 'react-icons/bs'; import {MdOutlineArrowBackIosNew} from 'react-icons/md'; import {RiMap2Line} from 'react-icons/ri'; import {useLocation, useNavigate} from 'react-router-dom'; +import {useRecoilValue} from 'recoil'; import styles from './VoteHeader.module.scss'; +import {isCandidateSelectingState} from '@/recoil/vote/alertModal'; + import {VoteHeaderProps} from '@/types/vote'; -const VoteHeader = ({onBottomSlideOpen, title, isNoCandidate}: VoteHeaderProps) => { +const VoteHeader = ({onBottomSlideOpen, title, isZeroCandidates}: VoteHeaderProps) => { const navigate = useNavigate(); const location = useLocation(); const path = location.pathname.split('/')[3]; + const isCandidateSelecting = useRecoilValue(isCandidateSelectingState); const setRightIcons = (path: string) => { switch (path) { @@ -26,10 +30,13 @@ const VoteHeader = ({onBottomSlideOpen, title, isNoCandidate}: VoteHeaderProps) default: return ( <> - - diff --git a/src/components/VoteMemo/MemoContent.tsx b/src/components/VoteMemo/MemoContent.tsx index ad8629cc..bd9c68cf 100644 --- a/src/components/VoteMemo/MemoContent.tsx +++ b/src/components/VoteMemo/MemoContent.tsx @@ -1,30 +1,24 @@ -import {useEffect} from 'react'; -import {useSetRecoilState} from 'recoil'; - import styles from './MemoContent.module.scss'; -import {selectedCandidatesState} from '@/recoil/vote/candidateList'; - import MemoItem from './MemoItem/MemoItem'; -import {VoteInfo} from '@/types/vote'; +import {TaglineType, VoteInfo} from '@/types/vote'; const MemoContent = ({data}: {data: VoteInfo}) => { - const setSelectedCandidates = useSetRecoilState(selectedCandidatesState); const candidates = data.candidates; - const CheckAllCandidates = () => { - const newCandidateIds = candidates.map((candidate) => candidate.id); - setSelectedCandidates(new Set(newCandidateIds)); - }; - - useEffect(() => { - CheckAllCandidates(); - }, []); + const getExistingTaglines = localStorage.getItem('recoil-persist'); + const existingTaglines: TaglineType[] = getExistingTaglines && JSON.parse(getExistingTaglines).selectedTaglineState; return (
- {candidates?.map((candidate) => )} + {candidates?.map((candidate) => ( + tagline.placeId === candidate.placeInfo.placeId)} + /> + ))}
); }; diff --git a/src/components/VoteMemo/MemoItem/MemoItem.tsx b/src/components/VoteMemo/MemoItem/MemoItem.tsx index 798e8d31..1ce907ba 100644 --- a/src/components/VoteMemo/MemoItem/MemoItem.tsx +++ b/src/components/VoteMemo/MemoItem/MemoItem.tsx @@ -1,49 +1,79 @@ import {Checkbox} from '@chakra-ui/react'; -import {useState} from 'react'; +import {useCallback, useEffect, useState} from 'react'; import styles from './MemoItem.module.scss'; -import useGetSelectedCandidates from '@/hooks/useGetSelectedCandidates'; +import {useDebounce} from '@/hooks/useDebounce'; +import useGetSelectedArray from '@/hooks/useGetSelectedArray'; -import {CandidatesInfo} from '@/types/vote'; +import {selectedTaglineState} from '@/recoil/vote/voteMemo'; -const MemoItem = ({candidate}: {candidate: CandidatesInfo}) => { - const [text, setText] = useState(0); +import {MemoItemProps} from '@/types/vote'; - const {addCandidateInSelectedList} = useGetSelectedCandidates(); +const MemoItem = ({candidate, existingTagline}: MemoItemProps) => { + const [text, setText] = useState(''); + // const [selectedTagline, setSelectedTagline] = useRecoilState(selectedTaglineState); + const {toggleItemInNewArray, setMemoArray} = useGetSelectedArray(selectedTaglineState); + const debouncedText = useDebounce(text, 500); + const placeInfo = candidate.placeInfo; + + useEffect(() => { + if (existingTagline) { + setText(existingTagline.tagline); + } + }, []); + + const handleCheckboxChange = () => { + toggleItemInNewArray({ + placeId: placeInfo.placeId, + tagline: debouncedText, + }); + }; + + const handleDebouncedTextChange = useCallback(() => { + setMemoArray({ + placeId: placeInfo.placeId, + tagline: debouncedText, + }); + }, [debouncedText, placeInfo.placeId, setMemoArray]); + + useEffect(() => { + handleDebouncedTextChange(); + }, [debouncedText]); return (
addCandidateInSelectedList(candidate.id)} + onChange={handleCheckboxChange} />
-
+
+