diff --git a/src/Main/components/DetailModal.tsx b/src/Main/components/DetailModal.tsx new file mode 100644 index 0000000..851a03d --- /dev/null +++ b/src/Main/components/DetailModal.tsx @@ -0,0 +1,150 @@ +import { + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, + Button, + Text, + Box, + Flex, + useBreakpointValue, +} from "@chakra-ui/react"; +import api from "../../api/interceptor"; + +interface DetailModalProps { + isOpen: boolean; + onClose: () => void; + content: { + id: number; + showId: string; + type: string; + title: string; + director: string; + cast: string; + country: string; + dateAdded: string; + releaseYear: string; + rating: string; + duration: string; + listedIn: string; + description: string; + } | null; +} + +const DetailModal = ({ isOpen, onClose, content }: DetailModalProps) => { + if (!content) return null; + + const handleLikeDislike = async (isLike: boolean) => { + try { + await api.post(`/api/like/${content.id}/${isLike}`); + console.log(isLike ? "좋아요 요청 성공" : "싫어요 요청 성공"); + } catch (error) { + console.error("좋아요/싫어요 요청 중 오류 발생:", error); + } + }; + + const handleWatchAndNavigate = async () => { + try { + // 시청 기록 저장 API 호출 + await api.post(`/api/watch/${content.id}`); + console.log("시청 기록 저장 요청 성공"); + } catch (error) { + console.error("시청 기록 저장 요청 중 오류 발생:", error); + } finally { + // 넷플릭스로 이동 + window.open( + "https://www.netflix.com/kr/", + "_blank", + "noopener,noreferrer" + ); + } + }; + + // 반응형 스타일 설정 + const textFontSize = useBreakpointValue({ base: "sm", md: "md" }); + const headerFontSize = useBreakpointValue({ base: "lg", md: "xl" }); + const buttonSize = useBreakpointValue({ base: "sm", md: "md" }); + const modalWidth = useBreakpointValue({ base: "90%", md: "500px" }); + + return ( + + + + {content.title} + + + + + Type: {content.type} + + + Director: {content.director} + + + Cast: {content.cast} + + + Country: {content.country} + + + Date Added: {content.dateAdded} + + + Release Year: {content.releaseYear} + + + Rating: {content.rating} + + + Duration: {content.duration} + + + Genres: {content.listedIn} + + + Description: {content.description} + + + + + {/* 좋아요/싫어요 버튼 */} + + + + + {/* 넷플릭스로 이동 버튼 */} + + + + + ); +}; + +export default DetailModal; diff --git a/src/Main/components/RandomContents.tsx b/src/Main/components/RandomContents.tsx index b2f2bc2..4832d43 100644 --- a/src/Main/components/RandomContents.tsx +++ b/src/Main/components/RandomContents.tsx @@ -1,14 +1,36 @@ import { useEffect, useState } from "react"; -import { Box, Grid, Text, Spinner, useBreakpointValue } from "@chakra-ui/react"; +import { + Box, + Grid, + Text, + Spinner, + useBreakpointValue, + useDisclosure, +} from "@chakra-ui/react"; import api from "../../api/interceptor"; +import DetailModal from "./DetailModal"; interface Content { + id: number; + showId: string; + type: string; title: string; + director: string; + cast: string; + country: string; + dateAdded: string; + releaseYear: string; + rating: string; + duration: string; + listedIn: string; + description: string; } function RandomContents(): JSX.Element { const [randomContents, setRandomContents] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [selectedContent, setSelectedContent] = useState(null); + const { isOpen, onOpen, onClose } = useDisclosure(); useEffect(() => { const fetchRandomContents = async () => { @@ -29,6 +51,11 @@ function RandomContents(): JSX.Element { fetchRandomContents(); }, []); + const handleCardClick = (content: Content) => { + setSelectedContent(content); + onOpen(); + }; + const cardColumns = useBreakpointValue({ base: "1fr", md: "repeat(5, 1fr)" }); return ( @@ -43,33 +70,50 @@ function RandomContents(): JSX.Element { ) : ( - - {randomContents.map((content, index) => ( - - + + {randomContents.map((content) => ( + handleCardClick(content)} > - {content.title} - - - ))} - + + {content.title} + + + ))} + + + )} ); diff --git a/src/Main/components/RecommendedContents.tsx b/src/Main/components/RecommendedContents.tsx index 10fa14e..4056f57 100644 --- a/src/Main/components/RecommendedContents.tsx +++ b/src/Main/components/RecommendedContents.tsx @@ -1,5 +1,186 @@ +import { useEffect, useState } from "react"; +import { Box, Grid, Text, Spinner, Flex } from "@chakra-ui/react"; +import api from "../../api/interceptor"; +import DetailModal from "./DetailModal"; // DetailModal 컴포넌트를 import + +interface Content { + id: number; + showId: string; + type: string; + title: string; + director: string; + cast: string; + country: string; + dateAdded: string; + releaseYear: string; + rating: string; + duration: string; + listedIn: string; + description: string; +} + +interface CategoryContent { + category: string; + contents: Content[]; +} + function RecommendedContents(): JSX.Element { - return
추천 콘텐츠 컴포넌트 내용
; + const [categories, setCategories] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedContent, setSelectedContent] = useState(null); // 선택된 콘텐츠 상태 + const [isModalOpen, setIsModalOpen] = useState(false); // 모달 상태 + + const categoryLabels: { [key: string]: string } = { + similarCastFromLikes: "🎭 좋아요한 콘텐츠와 출연진 유사", + similarGenreFromLikes: "🎬 좋아요한 콘텐츠와 장르 유사", + sameDirectorFromLikes: "🎥 좋아요한 콘텐츠와 감독 동일", + similarCastFromWatchHistory: "🌟 시청한 콘텐츠와 출연진 유사", + similarGenreFromWatchHistory: "📽️ 시청한 콘텐츠와 장르 유사", + sameDirectorFromWatchHistory: "🎞️ 시청한 콘텐츠와 감독 동일", + }; + + useEffect(() => { + const fetchRecommendedContents = async () => { + setIsLoading(true); + try { + const response = await api.get("/api/recommend/10"); + const fetchedCategories = Object.keys(categoryLabels).map( + (category) => ({ + category, + contents: + response.data[category]?.map( + (item: { content: Content }) => item.content + ) || [], + }) + ); + setCategories(fetchedCategories); + } catch (error) { + console.error("추천 콘텐츠를 가져오는 중 오류 발생:", error); + } finally { + setIsLoading(false); + } + }; + + fetchRecommendedContents(); + }, []); + + const handleCardClick = (content: Content) => { + setSelectedContent(content); // 선택된 콘텐츠 설정 + setIsModalOpen(true); // 모달 열기 + }; + + const closeModal = () => { + setSelectedContent(null); // 선택된 콘텐츠 초기화 + setIsModalOpen(false); // 모달 닫기 + }; + + if (isLoading) { + return ( + + + + ); + } + + return ( + + {categories.map(({ category, contents }) => ( + + {/* 카테고리 제목 */} + + {categoryLabels[category]} + + {contents.length > 0 ? ( + // 콘텐츠 리스트 + + + {contents.map((content) => ( + handleCardClick(content)} // 카드 클릭 핸들러 + transition="transform 0.2s, box-shadow 0.2s" + _hover={{ + transform: "scale(1.05)", + boxShadow: "lg", + borderColor: "teal.500", + }} + > + + {content.title} + + + ))} + + + ) : ( + // 데이터가 없는 경우 안내 메시지 + + 충분한 데이터가 쌓이지 않았습니다. + + )} + + ))} + + {/* DetailModal */} + {selectedContent && ( + + )} + + ); } export default RecommendedContents;