diff --git a/src/Component/Carousel/EmblaCarousel.tsx b/src/Component/Carousel/EmblaCarousel.tsx index 67ec0cb..0e85452 100644 --- a/src/Component/Carousel/EmblaCarousel.tsx +++ b/src/Component/Carousel/EmblaCarousel.tsx @@ -1,15 +1,15 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; -import { useNavigate } from "react-router-dom"; // useNavigate import 추가 +import { useNavigate } from "react-router-dom"; import { - EmblaCarouselType, - EmblaEventType, - EmblaOptionsType, + EmblaCarouselType, + EmblaEventType, + EmblaOptionsType, } from "embla-carousel"; import useEmblaCarousel from "embla-carousel-react"; import { - NextButton, - PrevButton, - usePrevNextButtons, + NextButton, + PrevButton, + usePrevNextButtons, } from "./EmblaCarouselArrowButtons"; import { DotButton, useDotButton } from "./EmblaCarouselDotButton"; import { formatPrice, formatROR } from "../../util/util"; @@ -19,206 +19,210 @@ import { usePortfolioStore } from "../../store/usePortfolioStore"; const TWEEN_FACTOR_BASE = 0.52; const numberWithinRange = (number: number, min: number, max: number): number => - Math.min(Math.max(number, min), max); + Math.min(Math.max(number, min), max); type PropType = { - data: { id: number; name: string; budget: number; ror: number }[]; - options?: EmblaOptionsType; - portfolioName: string; + data: { id: number; name: string; budget: number; ror: number }[]; + options?: EmblaOptionsType; + portfolioName: string; }; const EmblaCarousel: React.FC = ({ - data, - options, - portfolioName, + data, + options, + portfolioName, }) => { - const [emblaRef, emblaApi] = useEmblaCarousel(options); - const tweenFactor = useRef(0); - const tweenNodes = useRef([]); - const navigate = useNavigate(); // useNavigate hook 사용 - const [useless, setUseless] = useState(false); - - const { selectedIndex, scrollSnaps, onDotButtonClick } = - useDotButton(emblaApi); - - const change = usePortfolioStore((state) => state.change); - const setChange = usePortfolioStore((state) => state.setChange); - - const { - prevBtnDisabled, - nextBtnDisabled, - onPrevButtonClick, - onNextButtonClick, - } = usePrevNextButtons(emblaApi); - - const setTweenNodes = useCallback((emblaApi: EmblaCarouselType): void => { - tweenNodes.current = emblaApi - .slideNodes() - .map((slideNode, index) => { - const node = slideNode.querySelector(".embla__slide__number"); - if (node) { - return node as HTMLElement; - } else { - return null; - } - }) - .filter((node): node is HTMLElement => node !== null); - }, []); - - const setTweenFactor = useCallback((emblaApi: EmblaCarouselType) => { - tweenFactor.current = TWEEN_FACTOR_BASE * emblaApi.scrollSnapList().length; - }, []); - - const tweenScale = useCallback( - (emblaApi: EmblaCarouselType, eventName?: EmblaEventType) => { - const engine = emblaApi.internalEngine(); - const scrollProgress = emblaApi.scrollProgress(); - const slidesInView = emblaApi.slidesInView(); - const isScrollEvent = eventName === "scroll"; - - emblaApi.scrollSnapList().forEach((scrollSnap, snapIndex) => { - let diffToTarget = scrollSnap - scrollProgress; - const slidesInSnap = engine.slideRegistry[snapIndex]; - - slidesInSnap.forEach((slideIndex) => { - if (isScrollEvent && !slidesInView.includes(slideIndex)) return; - - if (engine.options.loop) { - engine.slideLooper.loopPoints.forEach((loopItem) => { - const target = loopItem.target(); - - if (slideIndex === loopItem.index && target !== 0) { - const sign = Math.sign(target); - - if (sign === -1) { - diffToTarget = scrollSnap - (1 + scrollProgress); + const [emblaRef, emblaApi] = useEmblaCarousel(options); + const tweenFactor = useRef(0); + const tweenNodes = useRef([]); + const navigate = useNavigate(); + const [useless, setUseless] = useState(false); + + const { selectedIndex, scrollSnaps, onDotButtonClick } = + useDotButton(emblaApi); + + const change = usePortfolioStore((state) => state.change); + const setChange = usePortfolioStore((state) => state.setChange); + + const { + prevBtnDisabled, + nextBtnDisabled, + onPrevButtonClick, + onNextButtonClick, + } = usePrevNextButtons(emblaApi); + + const setTweenNodes = useCallback((emblaApi: EmblaCarouselType): void => { + tweenNodes.current = emblaApi + .slideNodes() + .map((slideNode, index) => { + const node = slideNode.querySelector(".embla__slide__number"); + if (node) { + return node as HTMLElement; + } else { + return null; } - if (sign === 1) { - diffToTarget = scrollSnap + (1 - scrollProgress); - } - } + }) + .filter((node): node is HTMLElement => node !== null); + }, []); + + const setTweenFactor = useCallback((emblaApi: EmblaCarouselType) => { + tweenFactor.current = + TWEEN_FACTOR_BASE * emblaApi.scrollSnapList().length; + }, []); + + const tweenScale = useCallback( + (emblaApi: EmblaCarouselType, eventName?: EmblaEventType) => { + const engine = emblaApi.internalEngine(); + const scrollProgress = emblaApi.scrollProgress(); + const slidesInView = emblaApi.slidesInView(); + const isScrollEvent = eventName === "scroll"; + + emblaApi.scrollSnapList().forEach((scrollSnap, snapIndex) => { + let diffToTarget = scrollSnap - scrollProgress; + const slidesInSnap = engine.slideRegistry[snapIndex]; + + slidesInSnap.forEach((slideIndex) => { + if (isScrollEvent && !slidesInView.includes(slideIndex)) + return; + + if (engine.options.loop) { + engine.slideLooper.loopPoints.forEach((loopItem) => { + const target = loopItem.target(); + + if (slideIndex === loopItem.index && target !== 0) { + const sign = Math.sign(target); + + if (sign === -1) { + diffToTarget = + scrollSnap - (1 + scrollProgress); + } + if (sign === 1) { + diffToTarget = + scrollSnap + (1 - scrollProgress); + } + } + }); + } + + const tweenValue = + 1 - Math.abs(diffToTarget * tweenFactor.current); + const scale = numberWithinRange( + tweenValue, + 0, + 1 + ).toString(); + const tweenNode = tweenNodes.current[slideIndex]; + + if (tweenNode) { + tweenNode.style.transform = `scale(${scale})`; + } + }); }); - } - - const tweenValue = 1 - Math.abs(diffToTarget * tweenFactor.current); - const scale = numberWithinRange(tweenValue, 0, 1).toString(); - const tweenNode = tweenNodes.current[slideIndex]; - - if (tweenNode) { - tweenNode.style.transform = `scale(${scale})`; - } - }); - }); - }, - [tweenFactor, tweenNodes] - ); - - useEffect(() => { - if (!emblaApi) return; - - const timeoutId = setTimeout(() => { - setTweenNodes(emblaApi); - setTweenFactor(emblaApi); - tweenScale(emblaApi); - }, 100); - - emblaApi - .on("reInit", setTweenNodes) - .on("reInit", setTweenFactor) - .on("reInit", tweenScale) - .on("scroll", tweenScale) - .on("slideFocus", tweenScale); - - return () => { - clearTimeout(timeoutId); - emblaApi.off("reInit", setTweenNodes); - emblaApi.off("reInit", setTweenFactor); - emblaApi.off("reInit", tweenScale); - emblaApi.off("scroll", tweenScale); - emblaApi.off("slideFocus", tweenScale); + }, + [tweenFactor, tweenNodes] + ); + + useEffect(() => { + if (!emblaApi) return; + + const timeoutId = setTimeout(() => { + setTweenNodes(emblaApi); + setTweenFactor(emblaApi); + tweenScale(emblaApi); + }, 100); + + emblaApi + .on("reInit", setTweenNodes) + .on("reInit", setTweenFactor) + .on("reInit", tweenScale) + .on("scroll", tweenScale) + .on("slideFocus", tweenScale); + + return () => { + clearTimeout(timeoutId); + emblaApi.off("reInit", setTweenNodes); + emblaApi.off("reInit", setTweenFactor); + emblaApi.off("reInit", tweenScale); + emblaApi.off("scroll", tweenScale); + emblaApi.off("slideFocus", tweenScale); + }; + }, [emblaApi, tweenScale, setTweenNodes, setTweenFactor]); + + const handleSlideClick = (id: number) => { + navigate(`/portfolio/${id}`); }; - }, [emblaApi, tweenScale, setTweenNodes, setTweenFactor]); - - const handleSlideClick = (id: number) => { - navigate(`/portfolio/${id}`); // ID를 사용하여 라우팅 - }; - - // const deleteCarousel = async (id: number) => { - // const res = await axios.delete( - // `${process.env.REACT_APP_API_URL}/v1/portfolio/${id}`, - // { - // withCredentials: true, - // } - // ); - // if (res.status === 200) { - // alert("삭제성공"); - // setChange(!change); - // } - // }; - - return ( -
-
-
- {data.map((item) => { - const { value, color } = formatROR(item.ror); // formatROR 호출 - - return ( -
handleSlideClick(item.id)} - style={{ - cursor: "pointer", - position: "relative", - }} - > -
handleSlideClick(item.id)} - > -
-

{item.name}

-

- ₩ {formatPrice(item.budget)} -

-

{value}%

-
-
- Portfolio -
+ + return ( +
+
+
+ {data.map((item) => { + const { value, color } = formatROR(item.ror); + + return ( +
handleSlideClick(item.id)} + style={{ + cursor: "pointer", + position: "relative", + }} + > +
+
+

{item.name}

+

+ ₩ {formatPrice(item.budget)} +

+

{value}%

+ Portfolio +
+
+
+ ); + })}
-
- ); - })} -
-
- -
-
- {scrollSnaps.map((_, index) => ( - onDotButtonClick(index)} - className={"embla__dot".concat( - index === selectedIndex ? " embla__dot--selected" : "" - )} - /> - ))} -
-
- - +
+ +
+
+ {scrollSnaps.map((_, index) => { + const itemId = data[index]?.id; + return ( + onDotButtonClick(index)} + className={"embla__dot".concat( + index === selectedIndex + ? " embla__dot--selected" + : "" + )} + /> + ); + })} +
+
+ + +
+
-
-
- ); + ); }; export default EmblaCarousel; diff --git a/src/Component/Portfolio/PortfolioDetail.tsx b/src/Component/Portfolio/PortfolioDetail.tsx index d60a29f..a29be25 100644 --- a/src/Component/Portfolio/PortfolioDetail.tsx +++ b/src/Component/Portfolio/PortfolioDetail.tsx @@ -12,122 +12,124 @@ import DeleteButton2 from "../Button/DeleteButton2"; import styled from "styled-components"; const TitleDiv = styled.div` - width: 95%; - display: flex; - align-items: center; - position: relative; - margin-left: 1rem; - justify-content: space-between; - @media (max-width: 768px) { - width: 100%; - padding-right: 1rem; - } + width: 60%; + display: flex; + align-items: center; + position: relative; + justify-content: space-between; + @media (max-width: 768px) { + width: 100%; + padding-right: 1rem; + } `; const PortfolioDetail = () => { - const location = useLocation(); - const id = Number(location.pathname.split("/")[2]); - const nav = useNavigate(); - - const setPortfolio = usePortfolioStore((state) => state.setPortfolio); - const setFinancialData = usePortfolioStore((state) => state.setFinancialData); - const pfName = usePortfolioStore((state) => state.pfName); - const data = usePortfolioStore((state) => state.data); - const stockData = usePortfolioStore((state) => state.stockData); - const budget = usePortfolioStore((state) => state.budget); - const principal = usePortfolioStore((state) => state.principal); - const ret = usePortfolioStore((state) => state.ret); - const ror = usePortfolioStore((state) => state.ror); - const changeStatus = usePortfolioStore((state) => state.change); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const location = useLocation(); + const id = Number(location.pathname.split("/")[2]); + const nav = useNavigate(); - const change = usePortfolioStore((state) => state.change); - const setChange = usePortfolioStore((state) => state.setChange); + const setPortfolio = usePortfolioStore((state) => state.setPortfolio); + const setFinancialData = usePortfolioStore( + (state) => state.setFinancialData + ); + const pfName = usePortfolioStore((state) => state.pfName); + const data = usePortfolioStore((state) => state.data); + const stockData = usePortfolioStore((state) => state.stockData); + const budget = usePortfolioStore((state) => state.budget); + const principal = usePortfolioStore((state) => state.principal); + const ret = usePortfolioStore((state) => state.ret); + const ror = usePortfolioStore((state) => state.ror); + const changeStatus = usePortfolioStore((state) => state.change); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); - // const changeCheck = usePortfolioStore((state) => state.setChange); - const deletePortfolio = async (id: number) => { - try { - const res = await axios.delete( - `${process.env.REACT_APP_API_URL}/v1/portfolio/${id}`, - { - withCredentials: true, - } - ); + const change = usePortfolioStore((state) => state.change); + const setChange = usePortfolioStore((state) => state.setChange); - if (res.status === 200) { - setChange(!change); - swal({ - title: "삭제 완료!", - icon: "success", - }).then(() => { - nav("/portfolio"); - }); - setIsDeleteOpen(false); - } - } catch (error: any) { - if (error.response && error.response.status === 404) { - nav("/portfolio"); - } - } - }; + // const changeCheck = usePortfolioStore((state) => state.setChange); + const deletePortfolio = async (id: number) => { + try { + const res = await axios.delete( + `${process.env.REACT_APP_API_URL}/v1/portfolio/${id}`, + { + withCredentials: true, + } + ); - // 포트폴리오 상세 조회 - useEffect(() => { - axios - .get(`${process.env.REACT_APP_API_URL}/v1/portfolio/${id}`, { - withCredentials: true, - headers: { - "Content-Type": "application/json", - }, - }) - .then((res) => { - if (res.status === 200) { - setPortfolio(res.data.name, res.data, res.data.stocks); - setFinancialData( - res.data.budget, - res.data.principal, - res.data.ret, - res.data.ror - ); - } else if (res.status === 401) { - alert("401"); + if (res.status === 200) { + swal({ + title: "삭제 완료!", + icon: "success", + }).then(() => { + nav("/portfolio"); + setChange(!change); + }); + setIsDeleteOpen(false); + } + } catch (error: any) { + if (error.response && error.response.status === 404) { + nav("/portfolio"); + } } - }) - .catch((e) => { - // alert(e); - }); - }, [setPortfolio, setFinancialData, changeStatus]); + }; + + // 포트폴리오 상세 조회 + useEffect(() => { + axios + .get(`${process.env.REACT_APP_API_URL}/v1/portfolio/${id}`, { + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, + }) + .then((res) => { + if (res.status === 200) { + setPortfolio(res.data.name, res.data, res.data.stocks); + setFinancialData( + res.data.budget, + res.data.principal, + res.data.ret, + res.data.ror + ); + } else if (res.status === 401) { + alert("401"); + } + }) + .catch((e) => { + // alert(e); + }); + }, [setPortfolio, setFinancialData, changeStatus]); - if (!data) { - return
포트폴리오를 찾을 수 없습니다.
; - } + if (!data) { + return
포트폴리오를 찾을 수 없습니다.
; + } - return ( -
- -

{pfName}

- setIsDeleteOpen(true)} /> - {isDeleteOpen && ( - deletePortfolio(id)} // 삭제 함수 호출 - onRequestClose={() => setIsDeleteOpen(false)} // 모달 닫기 - showCancelButton={true} - /> - )} -
- - - -
- ); + const openDeleteModal = () => { + setIsDeleteOpen(true); + }; + return ( +
+ +

{pfName}

+ +
+ + + + deletePortfolio(id)} // 삭제 함수 호출 + onRequestClose={() => setIsDeleteOpen(false)} // 모달 닫기 + showCancelButton={true} + /> +
+ ); }; export default PortfolioDetail;