From f64b5fd8e6b99b3595e92d2efb349723d0259330 Mon Sep 17 00:00:00 2001 From: a-neey <2371429@hansung.ac.kr> Date: Mon, 11 May 2026 16:59:19 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20=ED=88=AC=ED=91=9C?= =?UTF-8?q?=EC=9E=90=20=EB=AA=A9=EB=A1=9D=EC=97=90=EC=84=9C=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/map/hooks/useVoteResult.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/pages/map/hooks/useVoteResult.ts b/src/pages/map/hooks/useVoteResult.ts index 23133e8..5309cf4 100644 --- a/src/pages/map/hooks/useVoteResult.ts +++ b/src/pages/map/hooks/useVoteResult.ts @@ -19,14 +19,17 @@ export const useVoteResult = ({ const { mutateAsync: deleteVote } = useDeleteVote(promiseId); const candidates: Candidate[] = candidatePlaces.map(c => ({ - id: c.id, - name: c.name, - distance: c.distance, - address: c.address, - createMember: c.voteInfo.creator.nickname, - voteMember: c.voteInfo.voters.map(v => v.nickname).join(', '), - voteCount: c.voteInfo.voteCount, - })); + id: c.id, + name: c.name, + distance: c.distance, + address: c.address, + createMember: c.voteInfo.creator.nickname, + voteMember: c.voteInfo.voters + .filter(v => v.userId !== c.voteInfo.creator.userId) // creator 제외 + .map(v => v.nickname) + .join(', '), + voteCount: c.voteInfo.voteCount, +})); const hasVote = candidates.some(c => c.voteCount > 0); const maxVote = Math.max(0, ...candidates.map(c => c.voteCount)); From e0c6f40a4ba1da4ea9aadbdeeea5dd8f8eb92af7 Mon Sep 17 00:00:00 2001 From: a-neey <2371429@hansung.ac.kr> Date: Mon, 11 May 2026 17:03:03 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=E2=9C=A8=20=EB=8B=A8=EC=9D=BC=20=ED=88=AC?= =?UTF-8?q?=ED=91=9C=20=EC=8B=9C=20=ED=88=AC=ED=91=9C=20=ED=9D=90=EB=A6=84?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/map/hooks/useVoteResult.ts | 62 ++++++++++++++++------------ 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/src/pages/map/hooks/useVoteResult.ts b/src/pages/map/hooks/useVoteResult.ts index 5309cf4..4710ca5 100644 --- a/src/pages/map/hooks/useVoteResult.ts +++ b/src/pages/map/hooks/useVoteResult.ts @@ -6,7 +6,6 @@ import type { Candidate, CardStatus } from '@/types/map'; interface UseVoteResultProps { candidatePlaces: CandidatePlace[]; isMultipleVoting: boolean; - isCreator: boolean; promiseId?: string; } @@ -19,17 +18,17 @@ export const useVoteResult = ({ const { mutateAsync: deleteVote } = useDeleteVote(promiseId); const candidates: Candidate[] = candidatePlaces.map(c => ({ - id: c.id, - name: c.name, - distance: c.distance, - address: c.address, - createMember: c.voteInfo.creator.nickname, - voteMember: c.voteInfo.voters - .filter(v => v.userId !== c.voteInfo.creator.userId) // creator 제외 - .map(v => v.nickname) - .join(', '), - voteCount: c.voteInfo.voteCount, -})); + id: c.id, + name: c.name, + distance: c.distance, + address: c.address, + createMember: c.voteInfo.creator.nickname, + voteMember: c.voteInfo.voters + .filter(v => v.userId !== c.voteInfo.creator.userId) + .map(v => v.nickname) + .join(', '), + voteCount: c.voteInfo.voteCount, + })); const hasVote = candidates.some(c => c.voteCount > 0); const maxVote = Math.max(0, ...candidates.map(c => c.voteCount)); @@ -43,41 +42,50 @@ export const useVoteResult = ({ const [isRevote, setIsRevote] = useState(false); const isRevoteTie = false; - const [hasVoted, setHasVoted] = useState(false); - const [myVote, setMyVote] = useState([]); + + // isMyVote로 초기 투표 상태 세팅 + const initialMyVote = candidatePlaces + .filter(c => c.voteInfo.isMyVote) + .map(c => c.id); + + const [hasVoted, setHasVoted] = useState(initialMyVote.length > 0); + const [myVote, setMyVote] = useState(initialMyVote); + const [previousVote, setPreviousVote] = useState(initialMyVote); const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); - const [confirmedCandidate, setConfirmedCandidate] = - useState(null); + const [confirmedCandidate, setConfirmedCandidate] = useState(null); const handleSelect = (id: number) => { if (isMultipleVoting) { - setMyVote(prev => - prev.includes(id) ? prev.filter(v => v !== id) : [...prev, id], - ); + setMyVote(prev => prev.includes(id) ? prev.filter(v => v !== id) : [...prev, id]); } else { - setMyVote(prev => (prev.includes(id) ? [] : [id])); + setMyVote(prev => prev.includes(id) ? [] : [id]); } }; - // 단일/복수 투표 제출 + // 기존 투표 취소 후 새로 투표 const handleVoteSubmit = async () => { if (myVote.length === 0) return; try { + if (previousVote.length > 0) { + await Promise.all(previousVote.map(id => deleteVote(id))); + } await Promise.all(myVote.map(id => postVote({ candidateId: id }))); + setPreviousVote(myVote); setHasVoted(true); } catch { - // 에러는 usePostVote onError에서 처리 + // 에러는 usePostVote onError에서 처리 } }; - // 단일/복수 투표 취소 + // 투표 취소 const handleVoteCancel = async () => { try { await Promise.all(myVote.map(id => deleteVote(id))); + setPreviousVote([]); setHasVoted(false); setMyVote([]); } catch { - // 에러는 useDeleteVote onError에서 처리 + // 에러는 useDeleteVote onError에서 처리 } }; @@ -105,8 +113,8 @@ export const useVoteResult = ({ } else { const target = myVote.length > 0 - ? candidates.find(c => c.id === myVote[0]) - : topCandidates[0]; + ? candidates.find(c => c.id === myVote[0]) + : topCandidates[0]; setConfirmedCandidate(target ?? null); setIsConfirmModalOpen(true); } @@ -129,4 +137,4 @@ export const useVoteResult = ({ handleVoteCancel, handleSelect, }; -}; +}; \ No newline at end of file From 141f6d17c4d52916ef962dac423ed4fb95a16e91 Mon Sep 17 00:00:00 2001 From: a-neey <2371429@hansung.ac.kr> Date: Mon, 11 May 2026 17:03:18 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9E=90=EC=97=90=20=EB=94=B0=EB=9D=BC=20=ED=97=A4=EB=8D=94=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/map/VoteResult.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/map/VoteResult.tsx b/src/pages/map/VoteResult.tsx index 8d7ccf4..cb9f997 100644 --- a/src/pages/map/VoteResult.tsx +++ b/src/pages/map/VoteResult.tsx @@ -38,13 +38,12 @@ const VoteResult = () => { } = useVoteResult({ candidatePlaces, isMultipleVoting: promise?.isMultipleVoting ?? true, - isCreator, promiseId, }); return (
-
+
From 473e0033906fa08f99e3f2c59424335aa0c5f9d3 Mon Sep 17 00:00:00 2001 From: a-neey <2371429@hansung.ac.kr> Date: Mon, 11 May 2026 17:16:29 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E2=9C=A8=20=EC=9E=A5=EC=86=8C=20=ED=99=95?= =?UTF-8?q?=EC=A0=95=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/map/votePlace.ts | 8 ++++- src/pages/map/ConfirmedResult.tsx | 42 +++++++++++++++----------- src/pages/map/PromiseMap.tsx | 4 +-- src/pages/map/VoteResult.tsx | 9 +++--- src/pages/map/hooks/useVoteResult.ts | 16 ++++++++-- src/pages/map/services/useVotePalce.ts | 22 ++++++++++++-- src/pages/promise/Promise.tsx | 4 +-- src/types/map/votePlace.type.ts | 5 +++ 8 files changed, 78 insertions(+), 32 deletions(-) diff --git a/src/apis/map/votePlace.ts b/src/apis/map/votePlace.ts index 65f9b58..85cbf9e 100644 --- a/src/apis/map/votePlace.ts +++ b/src/apis/map/votePlace.ts @@ -1,6 +1,6 @@ import { instance } from '../instance'; import type { ApiEnvelope, ApiEnvelopeNullable } from '@/types/api.type'; -import type { AddCandidatePlaceRequest, AddCandidatePlaceResponse, GetCandidatePlacesResponse, PostVoteRequest } from '@/types/map/votePlace.type'; +import type { AddCandidatePlaceRequest, AddCandidatePlaceResponse, ConfirmPlaceRequest, GetCandidatePlacesResponse, PostVoteRequest } from '@/types/map/votePlace.type'; // 투표 후보지 목록 조회 export const getCandidatePlaces = async (promiseId: string): Promise> => { @@ -31,3 +31,9 @@ export const deleteVote = async (promiseId: string, candidateId: number): Promis const response = await instance.delete(`/promises/${promiseId}/votes/${candidateId}`); return response.data; }; + +// 장소 확정 +export const confirmPlace = async (promiseId: string, body: ConfirmPlaceRequest): Promise> => { + const response = await instance.post(`/promises/${promiseId}/confirm`, body); + return response.data; +}; \ No newline at end of file diff --git a/src/pages/map/ConfirmedResult.tsx b/src/pages/map/ConfirmedResult.tsx index dfcb199..38245dd 100644 --- a/src/pages/map/ConfirmedResult.tsx +++ b/src/pages/map/ConfirmedResult.tsx @@ -1,20 +1,29 @@ import { useState } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import CandidatesCard from './components/CandidatesCard'; import CommonModal from '@/components/modal/CommonModal'; import PromiseStatusBadge from '@/components/common/PromiseStatusBadge'; import Header from '@/components/layout/Header'; +import { usePromiseDetail } from './services/usePromiseDetail'; +import { useGetCandidatePlaces } from './services/useVotePalce'; const ConfirmedResult = () => { - const { state } = useLocation(); - const promise = state?.promise; - const candidate = state?.confirmedCandidate; + const { promiseId } = useParams(); + const parsedPromiseId = Number(promiseId); + const { data: promise } = usePromiseDetail(parsedPromiseId); + const { data: candidatePlacesResponse } = useGetCandidatePlaces(promiseId); + const candidate = candidatePlacesResponse?.data.candidates.find(c => c.isConfirmed); const [isCalendarModalOpen, setIsCalendarModalOpen] = useState(false); - // 약속 날짜가 오늘인지 여부 - const today = - new Date().toDateString() === new Date(promise.date).toDateString(); + if (!promise || !candidate) return null; + + const today = new Date().toDateString() === new Date(promise.promisedAt).toDateString(); + const time = new Date(promise.promisedAt).toLocaleTimeString('ko-KR', { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); return (
@@ -42,7 +51,7 @@ const ConfirmedResult = () => { {promise.dayOfWeek}요일 {promise.title}

- 19:00 + {time}

@@ -56,9 +65,12 @@ const ConfirmedResult = () => { name={candidate.name} distance={candidate.distance} address={candidate.address} - createMember={candidate.createMember} - voteMember={candidate.voteMember} - voteCount={candidate.voteCount} + createMember={candidate.voteInfo.creator.nickname} + voteMember={candidate.voteInfo.voters + .filter(v => v.userId !== candidate.voteInfo.creator.userId) + .map(v => v.nickname) + .join(', ')} + voteCount={candidate.voteInfo.voteCount} memberCount={promise.memberCount} />
@@ -76,7 +88,6 @@ const ConfirmedResult = () => { ) : (
@@ -114,4 +122,4 @@ const ConfirmedResult = () => { ); }; -export default ConfirmedResult; +export default ConfirmedResult; \ No newline at end of file diff --git a/src/pages/map/PromiseMap.tsx b/src/pages/map/PromiseMap.tsx index 8e86905..ece8644 100644 --- a/src/pages/map/PromiseMap.tsx +++ b/src/pages/map/PromiseMap.tsx @@ -96,13 +96,13 @@ const PromiseMap = () => { const hasCheckedInitial = useRef(false); const [isCardExpanded, setIsCardExpanded] = useState(false); - const isConfirmed = false; // 확정된 장소 예정 - const { data: candidatePlacesResponse } = useGetCandidatePlaces(promiseId); const candidatePlaces = candidatePlacesResponse?.data.candidates; const candidatePlacesCount = candidatePlacesResponse?.data.candidateCount; console.log('candidatePlacesResponse: ', candidatePlacesResponse); + + const isConfirmed = candidatePlaces?.some(c => c.isConfirmed) ?? false; useEffect(() => { if (!candidatePlacesResponse) return; diff --git a/src/pages/map/VoteResult.tsx b/src/pages/map/VoteResult.tsx index cb9f997..fa56172 100644 --- a/src/pages/map/VoteResult.tsx +++ b/src/pages/map/VoteResult.tsx @@ -35,6 +35,7 @@ const VoteResult = () => { handleVoteSubmit, handleVoteCancel, handleSelect, + handleConfirm } = useVoteResult({ candidatePlaces, isMultipleVoting: promise?.isMultipleVoting ?? true, @@ -116,11 +117,11 @@ const VoteResult = () => { questionText="이 장소를 확정할까요?" mainText={confirmedCandidate.name} subText="확정 시 모든 멤버에게 알림이 전송됩니다" - onConfirm={() => { + onConfirm={async () => { + if (!confirmedCandidate) return; + await handleConfirm(confirmedCandidate.id); setIsConfirmModalOpen(false); - navigate(`/map/${promise?.id}/confirmed`, { - state: { promise, confirmedCandidate }, - }); + navigate(`/map/${promiseId}/confirmed`); }} onClose={() => setIsConfirmModalOpen(false)} confirmText="확정하기" diff --git a/src/pages/map/hooks/useVoteResult.ts b/src/pages/map/hooks/useVoteResult.ts index 4710ca5..6f3b797 100644 --- a/src/pages/map/hooks/useVoteResult.ts +++ b/src/pages/map/hooks/useVoteResult.ts @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { usePostVote, useDeleteVote } from '../services/useVotePalce'; +import { usePostVote, useDeleteVote, useConfirmPlace } from '../services/useVotePalce'; import type { CandidatePlace } from '@/types/map/votePlace.type'; import type { Candidate, CardStatus } from '@/types/map'; @@ -16,6 +16,8 @@ export const useVoteResult = ({ }: UseVoteResultProps) => { const { mutateAsync: postVote } = usePostVote(promiseId); const { mutateAsync: deleteVote } = useDeleteVote(promiseId); + const { mutateAsync: confirmPlace } = useConfirmPlace(promiseId); + const candidates: Candidate[] = candidatePlaces.map(c => ({ id: c.id, @@ -111,8 +113,7 @@ export const useVoteResult = ({ if (isTie && !isRevote && !isRevoteTie) { setIsRevote(true); } else { - const target = - myVote.length > 0 + const target = myVote.length > 0 ? candidates.find(c => c.id === myVote[0]) : topCandidates[0]; setConfirmedCandidate(target ?? null); @@ -120,6 +121,14 @@ export const useVoteResult = ({ } }; + // 모달에서 확정하기 누를 때 호출할 함수 + const handleConfirm = async (candidateId: number) => { + try { + await confirmPlace({ candidateId }); + } catch {} + }; + + return { sortedCandidates, myVote, @@ -136,5 +145,6 @@ export const useVoteResult = ({ handleVoteSubmit, handleVoteCancel, handleSelect, + handleConfirm }; }; \ No newline at end of file diff --git a/src/pages/map/services/useVotePalce.ts b/src/pages/map/services/useVotePalce.ts index c687758..6f55f18 100644 --- a/src/pages/map/services/useVotePalce.ts +++ b/src/pages/map/services/useVotePalce.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { addCandidatePlace, deleteCandidatePlace, deleteVote, getCandidatePlaces, postVote } from '@/apis/map/votePlace'; -import type { AddCandidatePlaceRequest, PostVoteRequest } from '@/types/map/votePlace.type'; +import { addCandidatePlace, confirmPlace, deleteCandidatePlace, deleteVote, getCandidatePlaces, postVote } from '@/apis/map/votePlace'; +import type { AddCandidatePlaceRequest, ConfirmPlaceRequest, PostVoteRequest } from '@/types/map/votePlace.type'; import axios from 'axios'; // 투표 후보지 목록 조회 @@ -106,3 +106,21 @@ export const useDeleteVote = (promiseId?: string) => { } }) }; + +// 장소 확정 +export const useConfirmPlace = (promiseId?: string) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (body: ConfirmPlaceRequest) => { + if (!promiseId) return Promise.reject(new Error("Promise ID is required")); + return confirmPlace(promiseId, body); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['candidatePlaces', promiseId] }); + queryClient.invalidateQueries({ queryKey: ['promise', Number(promiseId)] }); + }, + onError: (error) => { + console.error('장소 확정 실패: ', error); + }, + }); +}; \ No newline at end of file diff --git a/src/pages/promise/Promise.tsx b/src/pages/promise/Promise.tsx index 38cbc58..7dd74c2 100644 --- a/src/pages/promise/Promise.tsx +++ b/src/pages/promise/Promise.tsx @@ -37,9 +37,7 @@ const Promise = () => { title={promise.title} date={formatDate(promise.promisedAt, promise.dayOfWeek)} memberCount={promise.memberCount} - onClick={() => - navigate(`/map/${promise.id}/confirmed`, { state: { promise } }) - } + onClick={() => navigate(`/map/${promise.id}/confirmed`)} /> ))}
diff --git a/src/types/map/votePlace.type.ts b/src/types/map/votePlace.type.ts index 3eb33c8..af24e0a 100644 --- a/src/types/map/votePlace.type.ts +++ b/src/types/map/votePlace.type.ts @@ -52,3 +52,8 @@ export interface AddCandidatePlaceResponse { export interface PostVoteRequest { candidateId: number; }; + +// 장소 확정 +export interface ConfirmPlaceRequest { + candidateId: number; +} \ No newline at end of file From 5de59c6c6f1f09656e8e2bc8043448e00b64a9ad Mon Sep 17 00:00:00 2001 From: a-neey <2371429@hansung.ac.kr> Date: Mon, 11 May 2026 17:17:47 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=90=9B=20=EC=95=A4=EB=93=9C=20?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/map/votePlace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apis/map/votePlace.ts b/src/apis/map/votePlace.ts index 85cbf9e..48d71b1 100644 --- a/src/apis/map/votePlace.ts +++ b/src/apis/map/votePlace.ts @@ -34,6 +34,6 @@ export const deleteVote = async (promiseId: string, candidateId: number): Promis // 장소 확정 export const confirmPlace = async (promiseId: string, body: ConfirmPlaceRequest): Promise> => { - const response = await instance.post(`/promises/${promiseId}/confirm`, body); + const response = await instance.post(`/promises/${promiseId}/confirmed`, body); return response.data; }; \ No newline at end of file From 896fe77e51afe36bbf505f7fa3ee17467bba9d12 Mon Sep 17 00:00:00 2001 From: a-neey <2371429@hansung.ac.kr> Date: Mon, 11 May 2026 17:22:00 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=E2=9C=A8=20=EC=9E=AC=ED=88=AC=ED=91=9C=20A?= =?UTF-8?q?PI=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/map/votePlace.ts | 6 ++++++ src/pages/map/hooks/useVoteResult.ts | 7 ++++--- src/pages/map/services/useVotePalce.ts | 20 +++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/apis/map/votePlace.ts b/src/apis/map/votePlace.ts index 48d71b1..92b63d5 100644 --- a/src/apis/map/votePlace.ts +++ b/src/apis/map/votePlace.ts @@ -36,4 +36,10 @@ export const deleteVote = async (promiseId: string, candidateId: number): Promis export const confirmPlace = async (promiseId: string, body: ConfirmPlaceRequest): Promise> => { const response = await instance.post(`/promises/${promiseId}/confirmed`, body); return response.data; +}; + +// 재투표 +export const postRevote = async (promiseId: string): Promise> => { + const response = await instance.post(`/promises/${promiseId}/revote`); + return response.data; }; \ No newline at end of file diff --git a/src/pages/map/hooks/useVoteResult.ts b/src/pages/map/hooks/useVoteResult.ts index 6f3b797..6eba03a 100644 --- a/src/pages/map/hooks/useVoteResult.ts +++ b/src/pages/map/hooks/useVoteResult.ts @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { usePostVote, useDeleteVote, useConfirmPlace } from '../services/useVotePalce'; +import { usePostVote, useDeleteVote, useConfirmPlace, usePostRevote } from '../services/useVotePalce'; import type { CandidatePlace } from '@/types/map/votePlace.type'; import type { Candidate, CardStatus } from '@/types/map'; @@ -17,7 +17,7 @@ export const useVoteResult = ({ const { mutateAsync: postVote } = usePostVote(promiseId); const { mutateAsync: deleteVote } = useDeleteVote(promiseId); const { mutateAsync: confirmPlace } = useConfirmPlace(promiseId); - + const { mutateAsync: revote } = usePostRevote(promiseId); const candidates: Candidate[] = candidatePlaces.map(c => ({ id: c.id, @@ -109,8 +109,9 @@ export const useVoteResult = ({ const buttonDisabled = isRevote ? false : !hasVote; - const handleConfirmClick = () => { + const handleConfirmClick = async () => { if (isTie && !isRevote && !isRevoteTie) { + await revote(); // 재투표 setIsRevote(true); } else { const target = myVote.length > 0 diff --git a/src/pages/map/services/useVotePalce.ts b/src/pages/map/services/useVotePalce.ts index 6f55f18..02f0030 100644 --- a/src/pages/map/services/useVotePalce.ts +++ b/src/pages/map/services/useVotePalce.ts @@ -1,5 +1,5 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { addCandidatePlace, confirmPlace, deleteCandidatePlace, deleteVote, getCandidatePlaces, postVote } from '@/apis/map/votePlace'; +import { addCandidatePlace, confirmPlace, deleteCandidatePlace, deleteVote, getCandidatePlaces, postRevote, postVote } from '@/apis/map/votePlace'; import type { AddCandidatePlaceRequest, ConfirmPlaceRequest, PostVoteRequest } from '@/types/map/votePlace.type'; import axios from 'axios'; @@ -123,4 +123,22 @@ export const useConfirmPlace = (promiseId?: string) => { console.error('장소 확정 실패: ', error); }, }); +}; + +// 재투표 +export const usePostRevote = (promiseId?: string) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: () => { + if (!promiseId) return Promise.reject(new Error("Promise ID is required")); + return postRevote(promiseId); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['candidatePlaces', promiseId] }); + queryClient.invalidateQueries({ queryKey: ['promise', Number(promiseId)] }); + }, + onError: (error) => { + console.error('재투표 시작 실패: ', error); + }, + }); }; \ No newline at end of file From b1c58db1ad2ed9266c3112b4c839a83790f5cf24 Mon Sep 17 00:00:00 2001 From: a-neey <2371429@hansung.ac.kr> Date: Mon, 11 May 2026 17:49:16 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=E2=9C=A8=20=EB=93=9D=ED=91=9C=EC=88=98=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/map/PromiseMap.tsx | 7 ------- src/pages/map/components/VoteBottomSheet.tsx | 20 ++------------------ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/src/pages/map/PromiseMap.tsx b/src/pages/map/PromiseMap.tsx index ece8644..603be78 100644 --- a/src/pages/map/PromiseMap.tsx +++ b/src/pages/map/PromiseMap.tsx @@ -57,9 +57,6 @@ const PromiseMap = () => { }, [inviteCode, accessToken]); const { center, isOnline } = useMapLocation(); - const isMultipleVoting = promise?.isMultipleVoting ?? false; - - const { votedPlace, votedPlaces } = useVoteState({ isMultipleVoting }); const { isCommentMode, @@ -384,12 +381,8 @@ const PromiseMap = () => { isOpen={!isSheetOpen} onClose={() => setIsSheetOpen(false)} count={candidatePlacesCount} - promiseId={promiseId} - promise={promise} onGoResult={handleGoVoteResult} candidatesPlaces={candidatePlaces ?? []} - votedPlaces={[...votedPlaces]} - votedPlace={votedPlace} /> )} diff --git a/src/pages/map/components/VoteBottomSheet.tsx b/src/pages/map/components/VoteBottomSheet.tsx index 17f3ad1..16f29e9 100644 --- a/src/pages/map/components/VoteBottomSheet.tsx +++ b/src/pages/map/components/VoteBottomSheet.tsx @@ -7,34 +7,18 @@ interface VoteBottomSheetProps { isOpen: boolean; onClose: () => void; count: number; // 투표 중인 장소 수 - promiseId: string | undefined; - promise: any; // 타입 지정 예정 onGoResult: () => void; // 장소 결정하기 버튼 핸들러 candidatesPlaces: CandidatePlace[]; - votedPlaces: string[]; // 복수 투표된 장소 키 목록 - votedPlace: string | null; // 단일 투표된 장소 키 } const VoteBottomSheet = ({ isOpen, onClose, count, - promise, onGoResult, candidatesPlaces, - votedPlaces, - votedPlace, }: VoteBottomSheetProps) => { - // 마커별 득표 수 계산 - const getVoteCount = (candidatePlace: CandidatePlace): number => { - const key = `${candidatePlace.latitude}_${candidatePlace.longitude}`; - if (promise?.isMultipleVoting) { - return votedPlaces.filter(k => k === key).length; - } - return votedPlace === key ? 1 : 0; - }; - - const maxVote = Math.max(...candidatesPlaces.map(m => getVoteCount(m)), 0); + const maxVote = Math.max(...candidatesPlaces.map(c => c.voteInfo.voteCount), 0); return (
{candidatesPlaces.map((candidatePlace, i) => { - const vote = getVoteCount(candidatePlace); + const vote = candidatePlace.voteInfo.voteCount; return ( Date: Mon, 11 May 2026 17:52:50 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=90=9B=20=EB=B9=8C=EB=93=9C=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/map/PromiseMap.tsx | 1 - src/pages/map/hooks/useVoteState.ts | 64 ----------------------------- 2 files changed, 65 deletions(-) delete mode 100644 src/pages/map/hooks/useVoteState.ts diff --git a/src/pages/map/PromiseMap.tsx b/src/pages/map/PromiseMap.tsx index 603be78..600252d 100644 --- a/src/pages/map/PromiseMap.tsx +++ b/src/pages/map/PromiseMap.tsx @@ -12,7 +12,6 @@ import CommentBottomSheet from './components/CommentBottomSheet'; import LocationMarker from './components/LocationMarker'; import ToastMessage from '@/components/common/ToastMessage'; import { useMapLocation } from './hooks/useMapLocation'; -import { useVoteState } from './hooks/useVoteState'; import { useMapSheet } from './hooks/useMapSheet'; import { useComment } from './hooks/useComment'; import type { GetCommentsParams } from '@/types/map/comment.type'; diff --git a/src/pages/map/hooks/useVoteState.ts b/src/pages/map/hooks/useVoteState.ts deleted file mode 100644 index 4647c69..0000000 --- a/src/pages/map/hooks/useVoteState.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useState } from 'react'; -import type { Marker } from '@/types/map'; - -interface UseVoteStateProps { - isMultipleVoting: boolean; -} - -// 마커 목록 및 단일/복수 투표 상태 관리 -export const useVoteState = ({ isMultipleVoting }: UseVoteStateProps) => { - const [markers, setMarkers] = useState([]); - const [votedPlace, setVotedPlace] = useState(null); - const [votedPlaces, setVotedPlaces] = useState>(new Set()); - - // 좌표 기반 고유 키 생성 - const placeKey = (lat: number, lng: number) => `${lat}_${lng}`; - - // 후보지 마커 추가 또는 제거 - const handleToggleAdd = (isAdded: boolean, pendingPlace: Marker) => { - if (isAdded) { - setMarkers(prev => { - const alreadyExists = prev.some( - m => m.placeName === pendingPlace.placeName, - ); - if (alreadyExists) return prev; - return [...prev, { ...pendingPlace }]; - }); - } else { - setMarkers(prev => - prev.filter(m => m.placeName !== pendingPlace.placeName), - ); - } - }; - - // 단일/복수 투표 토글 - const handleToggleVote = (voted: boolean, pendingPlace: Marker) => { - const key = placeKey(pendingPlace.lat, pendingPlace.lng); - if (isMultipleVoting) { - setVotedPlaces(prev => { - const next = new Set(prev); - voted ? next.add(key) : next.delete(key); - return next; - }); - } else { - setVotedPlace(voted ? key : null); - } - }; - - // 현재 pendingPlace의 투표 여부 반환 - const getIsVoted = (pendingPlace: Marker | null): boolean => { - if (!pendingPlace) return false; - const key = placeKey(pendingPlace.lat, pendingPlace.lng); - return isMultipleVoting ? votedPlaces.has(key) : votedPlace === key; - }; - - return { - markers, - votedPlace, - votedPlaces, - placeKey, - handleToggleAdd, - handleToggleVote, - getIsVoted, - }; -};