diff --git a/src/api/useGetVoteDetail.ts b/src/api/useGetVoteDetail.ts index e13e109..dd43f12 100644 --- a/src/api/useGetVoteDetail.ts +++ b/src/api/useGetVoteDetail.ts @@ -1,6 +1,14 @@ import { useSuspenseQuery } from '@tanstack/react-query'; import { request } from './config'; +export interface Image { + id: number; + imageName: string; + imageUrl: string; + thumbnailUrl: string; + voted: boolean; +} + interface VoteDetailType { id: number; author: { @@ -9,12 +17,7 @@ interface VoteDetailType { profileUrl: string; }; description: string; - images: { - id: number; - imageName: string; - imageUrl: string; - voted: boolean; - }[]; + images: Image[]; shareUrl: string; createdAt: string; } diff --git a/src/components/common/Dialog/DialogProvider.tsx b/src/components/common/Dialog/DialogProvider.tsx index ad02a6a..c638707 100644 --- a/src/components/common/Dialog/DialogProvider.tsx +++ b/src/components/common/Dialog/DialogProvider.tsx @@ -28,7 +28,7 @@ export const DialogProvider = ({ children }: { children: React.ReactNode }) => { {createPortal( currentDialog && ( -
+
{currentDialog}
diff --git a/src/components/vote-detail/ImageDetailModal/ImageDetailModal.tsx b/src/components/vote-detail/ImageDetailModal/ImageDetailModal.tsx new file mode 100644 index 0000000..381c2b2 --- /dev/null +++ b/src/components/vote-detail/ImageDetailModal/ImageDetailModal.tsx @@ -0,0 +1,55 @@ +import useImageDetailModal from './hooks'; +import { Image } from '@/api/useGetVoteDetail'; +import Icon from '@/components/common/Icon'; + +interface ImageDetailModalProps { + images: Image[]; + selectedImageId: number; +} + +export default function ImageDetailModal({ + images, + selectedImageId, +}: ImageDetailModalProps) { + const { scrollContainerRef, currentIndex, handleScroll, closeDialog } = + useImageDetailModal({ + images, + selectedImageId, + }); + + return ( +
+
+ +
+ {currentIndex + 1} + / + {images.length} +
+
+
+ +
+ {images.map((image) => ( +
+ {`image-${image.id}`} +
+ ))} +
+
+ ); +} diff --git a/src/components/vote-detail/ImageDetailModal/hooks.ts b/src/components/vote-detail/ImageDetailModal/hooks.ts new file mode 100644 index 0000000..40fec78 --- /dev/null +++ b/src/components/vote-detail/ImageDetailModal/hooks.ts @@ -0,0 +1,50 @@ +import { useEffect, useRef, useState } from 'react'; +import { Image } from '@/api/useGetVoteDetail'; +import { useDialog } from '@/components/common/Dialog/hooks'; + +interface UseImageDetailModalOptions { + images: Image[]; + selectedImageId: number; +} + +export default function useImageDetailModal({ + images, + selectedImageId, +}: UseImageDetailModalOptions) { + const { closeDialog } = useDialog(); + + const [currentImageId, setCurrentImageId] = useState( + selectedImageId || images[0]?.id, + ); + + const scrollContainerRef = useRef(null); + + const currentIndex = images.findIndex((img) => img.id === currentImageId); + + const handleScroll = (e: React.UIEvent) => { + const container = e.currentTarget; + const itemWidth = container.clientWidth; + + const visibleIndex = Math.round(container.scrollLeft / itemWidth); + + if (images[visibleIndex] && images[visibleIndex].id !== currentImageId) { + setCurrentImageId(images[visibleIndex].id); + } + }; + + useEffect(() => { + if (scrollContainerRef.current && currentIndex !== -1) { + scrollContainerRef.current.scrollLeft = + currentIndex * scrollContainerRef.current.clientWidth; + } + }, [selectedImageId]); + + return { + scrollContainerRef, + imageNum: images.length, + images, + currentIndex, + handleScroll, + closeDialog, + }; +} diff --git a/src/components/vote-detail/ImageDetailModal/index.ts b/src/components/vote-detail/ImageDetailModal/index.ts new file mode 100644 index 0000000..81d7b61 --- /dev/null +++ b/src/components/vote-detail/ImageDetailModal/index.ts @@ -0,0 +1 @@ +export { default } from './ImageDetailModal'; diff --git a/src/components/vote-detail/Vote/VoteCard/VoteCardItem.tsx b/src/components/vote-detail/Vote/VoteCard/VoteCardItem.tsx index 8929b92..60df0bc 100644 --- a/src/components/vote-detail/Vote/VoteCard/VoteCardItem.tsx +++ b/src/components/vote-detail/Vote/VoteCard/VoteCardItem.tsx @@ -1,37 +1,36 @@ -import useVote from '@/api/useVote'; +import { HTMLAttributes } from 'react'; import Icon from '@/components/common/Icon'; import { Label } from '@/components/common/Label/Label'; import { cn } from '@/utils/cn'; -interface VoteCardItemProps { +interface VoteCardItemProps extends HTMLAttributes { image: { id: number; imageName: string; imageUrl: string; + thumbnailUrl: string; voted: boolean; }; - postId: number; + handleVote: (e: React.MouseEvent) => void; } -export default function VoteCardItem({ image, postId }: VoteCardItemProps) { - const { mutate: voteMutate } = useVote(postId); - - const handleVote = () => { - voteMutate(image.id); - }; - +export default function VoteCardItem({ + image, + onClick, + handleVote, +}: VoteCardItemProps) { return ( - // 추후에 사진 클릭 시 사진 확대 로직 들어가야함. -
- +
@@ -56,6 +55,6 @@ export default function VoteCardItem({ image, postId }: VoteCardItemProps) {
)} -
+ ); } diff --git a/src/components/vote-detail/Vote/VoteCard/VoteCardList.tsx b/src/components/vote-detail/Vote/VoteCard/VoteCardList.tsx index 4f6299a..ee2e2db 100644 --- a/src/components/vote-detail/Vote/VoteCard/VoteCardList.tsx +++ b/src/components/vote-detail/Vote/VoteCard/VoteCardList.tsx @@ -1,15 +1,43 @@ import { useParams } from 'react-router-dom'; import VoteCardItem from './VoteCardItem'; +import ImageDetailModal from '../../ImageDetailModal'; +import useVote from '@/api/useVote'; +import { useDialog } from '@/components/common/Dialog/hooks'; +import Loading from '@/components/common/Loading'; import useVoteDetail from '@/components/vote-detail/Vote/VoteCard/hooks'; export default function VoteCardList() { const { postId } = useParams<{ postId: string }>(); const { voteDetail } = useVoteDetail(Number(postId)); + const { mutate: voteMutate, isPending } = useVote(Number(postId)); + const { openDialog } = useDialog(); + + const handleClickVoteCardItem = (id: number) => { + openDialog( + , + ); + }; + + const handleVote = + (id: number) => (e: React.MouseEvent) => { + e.stopPropagation(); + voteMutate(id); + }; return ( -
+
+ {isPending && ( +
+ +
+ )} {voteDetail.images.map((image) => ( - + handleClickVoteCardItem(image.id)} + handleVote={handleVote(image.id)} + /> ))}
); diff --git a/src/components/vote-detail/Vote/VoteSection/VoteSection.tsx b/src/components/vote-detail/Vote/VoteSection/VoteSection.tsx index 7b326b5..e4c9154 100644 --- a/src/components/vote-detail/Vote/VoteSection/VoteSection.tsx +++ b/src/components/vote-detail/Vote/VoteSection/VoteSection.tsx @@ -8,6 +8,8 @@ export default function VoteSection() { <> }> + + }>