From 916454a870657afaa9447e3e514acf5e8d897a1c Mon Sep 17 00:00:00 2001 From: "yeondu._" Date: Fri, 30 Aug 2024 09:32:24 +0900 Subject: [PATCH 1/6] =?UTF-8?q?Style=20:=20=ED=99=94=EB=A9=B4=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EC=A6=88=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Button/DeleteButton.tsx | 1 + src/Component/List/Data/stockDataList.tsx | 2 +- src/Component/Portfolio/PfCardStyle.css | 66 ++++++++++----------- src/Component/Portfolio/PortfolioDetail.tsx | 3 +- src/Component/Swipe/Swipe.tsx | 2 +- src/Component/Swipe/SwipeStyle.css | 36 ++++++----- 6 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/Component/Button/DeleteButton.tsx b/src/Component/Button/DeleteButton.tsx index 50a3c58..55de04a 100644 --- a/src/Component/Button/DeleteButton.tsx +++ b/src/Component/Button/DeleteButton.tsx @@ -14,6 +14,7 @@ const DeleteButton: React.FC = ({ onClick }) => { left: "410px", cursor: "pointer", top: "-2px", + marginRight: "2rem", }} >
diff --git a/src/Component/List/Data/stockDataList.tsx b/src/Component/List/Data/stockDataList.tsx index 2da20a3..b4e5c1b 100644 --- a/src/Component/List/Data/stockDataList.tsx +++ b/src/Component/List/Data/stockDataList.tsx @@ -17,7 +17,7 @@ const ListWrapper = styled.div` const StockDataList: React.FC = ({ data }) => { return ( -
+
{data.map((item) => ( diff --git a/src/Component/Portfolio/PfCardStyle.css b/src/Component/Portfolio/PfCardStyle.css index 80e6b87..a233f18 100644 --- a/src/Component/Portfolio/PfCardStyle.css +++ b/src/Component/Portfolio/PfCardStyle.css @@ -1,63 +1,63 @@ .PfCard { - width: 388px; - height: 260px; - border-radius: 20px; - background-color: #615efc; - padding: 37px 17px; - box-shadow: 2px 5px 5px rgba(0, 0, 0, 0.35); + width: 100%; + height: 260px; + border-radius: 20px; + background-color: #615efc; + padding: 37px 17px; + box-shadow: 2px 5px 5px rgba(0, 0, 0, 0.35); } .PfCard h3 { - font-size: 20px; - color: #e0e0e0; - margin-bottom: 14px; + font-size: 20px; + color: #e0e0e0; + margin-bottom: 14px; } .PfCard p { - font-size: 25px; - color: #ffffff; - font-weight: 700; + font-size: 25px; + color: #ffffff; + font-weight: 700; } .detail-section { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - gap: 14px; - margin-top: 50px; - width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 14px; + margin-top: 50px; + width: 100%; } .total-value, .gain-and-loss { - display: flex; - flex-direction: row; - width: 100%; + display: flex; + flex-direction: row; + width: 100%; } .title-area { - flex: 1; - color: #cbcbcb; - font-size: 15px; + flex: 1; + color: #cbcbcb; + font-size: 15px; } .value-area { - font-size: 18px; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 5px; + font-size: 18px; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 5px; } .total-value .value-area { - color: #ffffff; + color: #ffffff; } .gain-and-loss .value-area { - color: red; + color: red; } .value-area > p { - font-size: 19px; + font-size: 19px; } diff --git a/src/Component/Portfolio/PortfolioDetail.tsx b/src/Component/Portfolio/PortfolioDetail.tsx index 5906724..6f05b0c 100644 --- a/src/Component/Portfolio/PortfolioDetail.tsx +++ b/src/Component/Portfolio/PortfolioDetail.tsx @@ -11,10 +11,11 @@ import DeleteButton from "../Button/DeleteButton"; import styled from "styled-components"; const TitleDiv = styled.div` - width: 60%; + width: 95%; display: flex; align-items: center; position: relative; + margin-left: 1rem; justify-content: space-between; @media (max-width: 768px) { width: 100%; diff --git a/src/Component/Swipe/Swipe.tsx b/src/Component/Swipe/Swipe.tsx index 82fe2bf..4f4d2e5 100644 --- a/src/Component/Swipe/Swipe.tsx +++ b/src/Component/Swipe/Swipe.tsx @@ -71,7 +71,7 @@ const Swipe: React.FC = ({ portfolioId }) => { }; return ( -
+
Date: Fri, 30 Aug 2024 10:07:07 +0900 Subject: [PATCH 2/6] =?UTF-8?q?Style=20:=20=EC=8A=A4=EC=99=80=EC=9D=B4?= =?UTF-8?q?=ED=94=84=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=ED=81=AC=EA=B8=B0=20?= =?UTF-8?q?css=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Modal/modalStyle.ts | 5 +++++ src/Component/Portfolio/PfCardStyle.css | 8 +++++++- src/Component/Swipe/SwipeStyle.css | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Component/Modal/modalStyle.ts b/src/Component/Modal/modalStyle.ts index a54499f..157e8cd 100644 --- a/src/Component/Modal/modalStyle.ts +++ b/src/Component/Modal/modalStyle.ts @@ -15,6 +15,7 @@ export const ModalStyles: ReactModal.Styles = { height: "450px", zIndex: "11", position: "fixed", + scrollbarWidth: "none", top: "50%", left: "50%", transform: "translate(-50%, -50%)", @@ -28,6 +29,10 @@ export const ModalHeader = styled.div` align-items: center; padding-bottom: 10px; margin-bottom: 20px; + ::-webkit-scrollbar { + width: 0px; + height: 0px; + } h2 { margin: 0; diff --git a/src/Component/Portfolio/PfCardStyle.css b/src/Component/Portfolio/PfCardStyle.css index a233f18..e463796 100644 --- a/src/Component/Portfolio/PfCardStyle.css +++ b/src/Component/Portfolio/PfCardStyle.css @@ -1,5 +1,5 @@ .PfCard { - width: 100%; + width: 90%; height: 260px; border-radius: 20px; background-color: #615efc; @@ -7,6 +7,12 @@ box-shadow: 2px 5px 5px rgba(0, 0, 0, 0.35); } +@media screen and (max-width: 768px) { + .PfCard { + width: 100%; + } +} + .PfCard h3 { font-size: 20px; color: #e0e0e0; diff --git a/src/Component/Swipe/SwipeStyle.css b/src/Component/Swipe/SwipeStyle.css index 7d98138..b4ea9c7 100644 --- a/src/Component/Swipe/SwipeStyle.css +++ b/src/Component/Swipe/SwipeStyle.css @@ -15,6 +15,13 @@ z-index: 1; } +@media screen and (max-width: 768px) { + .swipeableContainer { + width: 90%; + left: 47%; + } +} + .swipeHandle { display: flex; flex-direction: row; From a9c66a59675e9256a827e26416b5eeff30fa2d5f Mon Sep 17 00:00:00 2001 From: "yeondu._" Date: Fri, 30 Aug 2024 10:29:48 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Style=20:=20=EC=9E=90=EB=A6=AC=20=EB=84=98?= =?UTF-8?q?=EC=96=B4=EA=B0=80=EB=A9=B4=20=EC=95=84=EB=9E=AB=EC=A4=84?= =?UTF-8?q?=EB=A1=9C=20=EB=84=98=EC=96=B4=EA=B0=80=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Carousel/EmblaCarousel.tsx | 1 + src/Component/Carousel/embla.css | 2 + src/Component/List/MyStockItem.tsx | 532 +++++++++++------------ src/Component/Portfolio/PfCard.tsx | 50 +-- src/Component/Portfolio/portfolio.tsx | 2 +- src/util/util.ts | 68 +-- 6 files changed, 324 insertions(+), 331 deletions(-) diff --git a/src/Component/Carousel/EmblaCarousel.tsx b/src/Component/Carousel/EmblaCarousel.tsx index d7142c6..67ec0cb 100644 --- a/src/Component/Carousel/EmblaCarousel.tsx +++ b/src/Component/Carousel/EmblaCarousel.tsx @@ -183,6 +183,7 @@ const EmblaCarousel: React.FC = ({ style={{ fontSize: "20px", color: "black", + wordWrap: "break-word", }} > ₩ {formatPrice(item.budget)} diff --git a/src/Component/Carousel/embla.css b/src/Component/Carousel/embla.css index 7293788..4d5ef7f 100644 --- a/src/Component/Carousel/embla.css +++ b/src/Component/Carousel/embla.css @@ -40,6 +40,7 @@ margin-top: 0.7rem; margin-bottom: 1rem; min-height: 75px; + word-wrap: break-word; } @media screen and (max-width: 768px) { @@ -48,6 +49,7 @@ margin-top: 0.7rem; margin-bottom: 1rem; min-height: 40px; + word-wrap: break-word; } } diff --git a/src/Component/List/MyStockItem.tsx b/src/Component/List/MyStockItem.tsx index 7affd3d..3a3325e 100644 --- a/src/Component/List/MyStockItem.tsx +++ b/src/Component/List/MyStockItem.tsx @@ -11,290 +11,284 @@ import { useNavigate } from "react-router-dom"; import { usePortfolioStore } from "../../store/usePortfolioStore"; const MyStockItem: React.FC = ({ - code, - name, - quantity, - average, - ror, - portfolioId, - logo, + code, + name, + quantity, + average, + ror, + portfolioId, + logo, }) => { - const [isFormOpen, setIsFormOpen] = useState(false); - const [isPlusOpen, setIsPlusOpen] = useState(false); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); - const [selectedStock, setSelectedStock] = useState(null); + const [isFormOpen, setIsFormOpen] = useState(false); + const [isPlusOpen, setIsPlusOpen] = useState(false); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const [selectedStock, setSelectedStock] = useState(null); - const [modalAction, setModalAction] = useState< - "edit" | "delete" | "plus" | null - >(null); - const navigate = useNavigate(); + const [modalAction, setModalAction] = useState< + "edit" | "delete" | "plus" | null + >(null); + const navigate = useNavigate(); - const addStockToStore = usePortfolioStore((state) => state.addStock); - const updateStockInStore = usePortfolioStore((state) => state.updateStock); - const deleteStockFromStore = usePortfolioStore( - (state) => state.deleteStock - ); - const calculateROR = usePortfolioStore((state) => state.calculateROR); - const setChange = usePortfolioStore((state) => state.setChange); - const change = usePortfolioStore((state) => state.change); + const addStockToStore = usePortfolioStore((state) => state.addStock); + const updateStockInStore = usePortfolioStore((state) => state.updateStock); + const deleteStockFromStore = usePortfolioStore((state) => state.deleteStock); + const calculateROR = usePortfolioStore((state) => state.calculateROR); + const setChange = usePortfolioStore((state) => state.setChange); + const change = usePortfolioStore((state) => state.change); - const userStocks: PlusProps[] = [ - { - code, - name, - quantity, - average, - portfolioId, - logo: logo || "default_logo", - }, - ]; - - const handleConfirm = (newQuantity: number, newPrice: number) => { - if (modalAction === "plus") { - axios - .patch( - `${process.env.REACT_APP_API_URL}/v1/portfolio/${portfolioId}/holding/${code}`, - { quantity: newQuantity, price: newPrice }, - { withCredentials: true } - ) - .then((res) => { - if (res.status === 200) { - const updatedStock: StockProps = { - code, - name, - quantity: newQuantity, - average: newPrice, - ror, - portfolioId, - logo, - }; - addStockToStore(updatedStock); - calculateROR(); - setChange(!change); - swal({ - title: "추가 등록완료!", - icon: "success", - }); - setIsPlusOpen(false); - setSelectedStock(null); - setModalAction(null); - } - }) - .catch((error) => { - swal({ - title: "등록에 실패하셨습니다.", - text: "다시 시도해주세요!", - icon: "error", - }); - setSelectedStock(null); - setModalAction(null); - }); - } else if (modalAction === "edit") { - axios - .put( - `${process.env.REACT_APP_API_URL}/v1/portfolio/${portfolioId}/holding/${code}`, - { quantity: newQuantity, price: newPrice }, - { withCredentials: true } - ) - .then((res) => { - if (res.status === 200) { - const oldStockValue = quantity * average; - const newStockValue = newQuantity * newPrice; - const valueDifference = newStockValue - oldStockValue; + const userStocks: PlusProps[] = [ + { + code, + name, + quantity, + average, + portfolioId, + logo: logo || "default_logo", + }, + ]; - const updatedStock: StockProps = { - code, - name, - quantity: newQuantity, - average: newPrice, - ror, - portfolioId, - logo, - }; - updateStockInStore(updatedStock); - calculateROR(); - setChange(!change); - swal({ - title: "수정 완료!", - icon: "success", - }); - setIsFormOpen(false); - } else if (res.status === 401) { - swal({ - title: "수정 실패하셨습니다.", - text: "다시 시도해주세요!", - icon: "error", - }).then(() => { - navigate("/login"); - }); - } - }) - .catch((error) => { - if (error.response && error.response.status === 401) { - swal({ - title: "Unauthorized", - text: "로그인을 다시 시도해주세요.", - icon: "warning", - }).then(() => { - navigate("/login"); - }); - } else { - swal({ - title: "Edit failed", - text: "다시 시도해주세요", - icon: "error", - }); - setSelectedStock(null); - setModalAction(null); - } - }); - } - }; + const handleConfirm = (newQuantity: number, newPrice: number) => { + if (modalAction === "plus") { + axios + .patch( + `${process.env.REACT_APP_API_URL}/v1/portfolio/${portfolioId}/holding/${code}`, + { quantity: newQuantity, price: newPrice }, + { withCredentials: true } + ) + .then((res) => { + if (res.status === 200) { + const updatedStock: StockProps = { + code, + name, + quantity: newQuantity, + average: newPrice, + ror, + portfolioId, + logo, + }; + addStockToStore(updatedStock); + calculateROR(); + setChange(!change); + swal({ + title: "추가 등록완료!", + icon: "success", + }); + setIsPlusOpen(false); + setSelectedStock(null); + setModalAction(null); + } + }) + .catch((error) => { + swal({ + title: "등록에 실패하셨습니다.", + text: "다시 시도해주세요!", + icon: "error", + }); + setSelectedStock(null); + setModalAction(null); + }); + } else if (modalAction === "edit") { + axios + .put( + `${process.env.REACT_APP_API_URL}/v1/portfolio/${portfolioId}/holding/${code}`, + { quantity: newQuantity, price: newPrice }, + { withCredentials: true } + ) + .then((res) => { + if (res.status === 200) { + const oldStockValue = quantity * average; + const newStockValue = newQuantity * newPrice; + const valueDifference = newStockValue - oldStockValue; - const deleteHandle = () => { - axios - .delete( - `${process.env.REACT_APP_API_URL}/v1/portfolio/${portfolioId}/holding/${code}`, - { - withCredentials: true, - } - ) - .then((res) => { - if (res.status === 200) { - const deletedStockValue = quantity * average; + const updatedStock: StockProps = { + code, + name, + quantity: newQuantity, + average: newPrice, + ror, + portfolioId, + logo, + }; + updateStockInStore(updatedStock); + calculateROR(); + setChange(!change); + swal({ + title: "수정 완료!", + icon: "success", + }); + setIsFormOpen(false); + } else if (res.status === 401) { + swal({ + title: "수정 실패하셨습니다.", + text: "다시 시도해주세요!", + icon: "error", + }).then(() => { + navigate("/login"); + }); + } + }) + .catch((error) => { + if (error.response && error.response.status === 401) { + swal({ + title: "Unauthorized", + text: "로그인을 다시 시도해주세요.", + icon: "warning", + }).then(() => { + navigate("/login"); + }); + } else { + swal({ + title: "Edit failed", + text: "다시 시도해주세요", + icon: "error", + }); + setSelectedStock(null); + setModalAction(null); + } + }); + } + }; - deleteStockFromStore(code, deletedStockValue); + const deleteHandle = () => { + axios + .delete( + `${process.env.REACT_APP_API_URL}/v1/portfolio/${portfolioId}/holding/${code}`, + { + withCredentials: true, + } + ) + .then((res) => { + if (res.status === 200) { + const deletedStockValue = quantity * average; - calculateROR(); - setChange(!change); + deleteStockFromStore(code, deletedStockValue); - swal({ - title: "삭제 완료!", - icon: "success", - }); - setIsDeleteOpen(false); - setSelectedStock(null); - setModalAction(null); - } - }) - .catch((error) => { - swal({ - title: "삭제에 실패하셨습니다.", - text: "다시 시도해주세요!", - icon: "error", - }); - setSelectedStock(null); - setModalAction(null); - }); - }; + calculateROR(); + setChange(!change); - const openModal = (action: "edit" | "delete" | "plus") => { - setModalAction(action); - setSelectedStock({ - portfolioId, - code, - name, - quantity, - average, - ror, - logo: selectedStock?.logo || logo, - }); - if (action === "delete") { - setIsDeleteOpen(true); - } else if (action === "plus") { - setIsPlusOpen(true); - } else { - setIsFormOpen(true); + swal({ + title: "삭제 완료!", + icon: "success", + }); + setIsDeleteOpen(false); + setSelectedStock(null); + setModalAction(null); } - }; + }) + .catch((error) => { + swal({ + title: "삭제에 실패하셨습니다.", + text: "다시 시도해주세요!", + icon: "error", + }); + setSelectedStock(null); + setModalAction(null); + }); + }; - return ( -
-
- - - -
-
- {logo ? ( - {`${name} - ) : ( -
- {name.charAt(0)} -
- )} -
-

{name}

-

{code}

-
-
- {`${formatROR(ror).value} %`} -
-
-

수량 {formatPrice(quantity)}

-
{formatPrice(average)}원
-

- {formatPrice( - quantity * average * (1 + ror / 100) - - quantity * average - )} - 원 -

-
-
- {isFormOpen && selectedStock && ( - setIsFormOpen(false)} - onConfirm={handleConfirm} - action={modalAction === "edit" ? "edit" : undefined} - selectedStock={selectedStock} - /> - )} + const openModal = (action: "edit" | "delete" | "plus") => { + setModalAction(action); + setSelectedStock({ + portfolioId, + code, + name, + quantity, + average, + ror, + logo: selectedStock?.logo || logo, + }); + if (action === "delete") { + setIsDeleteOpen(true); + } else if (action === "plus") { + setIsPlusOpen(true); + } else { + setIsFormOpen(true); + } + }; - {isPlusOpen && ( - setIsPlusOpen(false)} - onConfirm={handleConfirm} - userStocks={userStocks} - /> - )} - - {isDeleteOpen && ( - setIsDeleteOpen(false)} - showCancelButton={true} - /> + return ( +
+
+ + + +
+
+ {logo ? ( + {`${name} + ) : ( +
+ {name.charAt(0)} +
+ )} +
+

{name}

+

{code}

+
+
+ {`${formatROR(ror).value} %`} +
+
+

수량 {formatPrice(quantity)}

+
{formatPrice(average)}원
+

+ {formatPrice( + quantity * average * (1 + ror / 100) - quantity * average )} + 원 +

- ); +
+ {isFormOpen && selectedStock && ( + setIsFormOpen(false)} + onConfirm={handleConfirm} + action={modalAction === "edit" ? "edit" : undefined} + selectedStock={selectedStock} + /> + )} + + {isPlusOpen && ( + setIsPlusOpen(false)} + onConfirm={handleConfirm} + userStocks={userStocks} + /> + )} + + {isDeleteOpen && ( + setIsDeleteOpen(false)} + showCancelButton={true} + /> + )} +
+ ); }; export default MyStockItem; diff --git a/src/Component/Portfolio/PfCard.tsx b/src/Component/Portfolio/PfCard.tsx index 8998685..7cfbfe2 100644 --- a/src/Component/Portfolio/PfCard.tsx +++ b/src/Component/Portfolio/PfCard.tsx @@ -4,35 +4,31 @@ import { usePortfolioStore } from "../../store/usePortfolioStore"; import { getGrowthColor, formatROR } from "../../util/util"; const PfCard: React.FC = () => { - const budget = usePortfolioStore((state) => state.budget); - const principal = usePortfolioStore((state) => state.principal); - const profitLoss = usePortfolioStore((state) => state.ret); - const ror = usePortfolioStore((state) => state.ror); - const { value: formattedROR, color } = formatROR(ror); + const budget = usePortfolioStore((state) => state.budget); + const principal = usePortfolioStore((state) => state.principal); + const profitLoss = usePortfolioStore((state) => state.ret); + const ror = usePortfolioStore((state) => state.ror); + const { value: formattedROR, color } = formatROR(ror); - return ( -
-

총 자산

-

₩ {budget.toLocaleString()}

-
-
-
투자 총 금액
-
- ₩ {principal.toLocaleString()} -
-
-
-
평가 손익
-
-

{`₩ ${profitLoss.toLocaleString()}`}

-

{`(${formattedROR}%)`}

-
-
-
+ return ( +
+

총 자산

+

₩ {budget.toLocaleString()}

+
+
+
투자 총 금액
+
₩ {principal.toLocaleString()}
- ); +
+
평가 손익
+
+

{`₩ ${profitLoss.toLocaleString()}`}

+

{`(${formattedROR}%)`}

+
+
+
+
+ ); }; export default PfCard; diff --git a/src/Component/Portfolio/portfolio.tsx b/src/Component/Portfolio/portfolio.tsx index 0b54860..25d3004 100644 --- a/src/Component/Portfolio/portfolio.tsx +++ b/src/Component/Portfolio/portfolio.tsx @@ -133,7 +133,7 @@ const Portfolio = () => {
diff --git a/src/util/util.ts b/src/util/util.ts index a416058..531a33e 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,28 +1,28 @@ export const getGrowthColor = (growth: number): string => { - if (growth > 0) { - return "#FF5759"; - } else if (growth < 0) { - return "#21BF73"; - } else { - return "black"; - } + if (growth > 0) { + return "#FF5759"; + } else if (growth < 0) { + return "#21BF73"; + } else { + return "black"; + } }; export const formatPrice = (price: number | undefined | null): string => { - if (price === undefined || price === null) { - return "0"; // price가 undefined나 null일 경우 기본값 반환 - } - return `${price.toLocaleString()}`; + if (price === undefined || price === null) { + return "0"; // price가 undefined나 null일 경우 기본값 반환 + } + return `${price.toLocaleString()}`; }; export const formatRate = (rate: number | undefined | null): string => { - if (rate === undefined || rate === null) { - return "0.00"; // Return "0.00" if rate is undefined or null - } - return rate.toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }); + if (rate === undefined || rate === null) { + return "0.00"; // Return "0.00" if rate is undefined or null + } + return rate.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); }; // export const formatROR = (ror: number): string => { @@ -31,27 +31,27 @@ export const formatRate = (rate: number | undefined | null): string => { // } type FormattedROR = { - value: string; - color: string; + value: string; + color: string; }; export const formatROR = (ror: number | undefined): FormattedROR => { - // 기본값 설정 - if (ror === undefined || ror === null) { - return { value: "0.00", color: "blue" }; - } + // 기본값 설정 + if (ror === undefined || ror === null) { + return { value: "0.00", color: "blue" }; + } - // 양수, 음수, 0 판단 - const isPositive = ror > 0; - const isZero = ror === 0; + // 양수, 음수, 0 판단 + const isPositive = ror > 0; + const isZero = ror === 0; - // + 붙이기, 2자리 소수점으로 표시, 0일 때는 기호 없이 표시 - const formattedValue = isZero - ? ror.toFixed(2) - : `${isPositive ? "+" : ""}${ror.toFixed(2)}`; + // + 붙이기, 2자리 소수점으로 표시, 0일 때는 기호 없이 표시 + const formattedValue = isZero + ? ror.toFixed(2) + : `${isPositive ? "+" : ""}${ror.toFixed(2)}`; - // 색상 설정, 0일 때는 회색 - const color = isZero ? "#5B5B5B" : isPositive ? "#FF4949" : "#001AFF"; + // 색상 설정, 0일 때는 회색 + const color = isZero ? "#5B5B5B" : isPositive ? "#FF4949" : "#001AFF"; - return { value: formattedValue, color: color }; + return { value: formattedValue, color: color }; }; From 1090744a4017dd1ed9bd949f5744117a4443b1e3 Mon Sep 17 00:00:00 2001 From: "yeondu._" Date: Fri, 30 Aug 2024 11:01:31 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Style=20:=20=EB=AA=A8=EB=8B=AC=EC=B0=BD=20?= =?UTF-8?q?=EB=AA=A8=EB=B0=94=EC=9D=BC=EB=B7=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Modal/AddPortfolio.tsx | 2 +- src/Component/Modal/modal.tsx | 85 ++++++++++++++++++---------- 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/src/Component/Modal/AddPortfolio.tsx b/src/Component/Modal/AddPortfolio.tsx index 7105490..364b5da 100644 --- a/src/Component/Modal/AddPortfolio.tsx +++ b/src/Component/Modal/AddPortfolio.tsx @@ -77,7 +77,7 @@ const AddPortfolioModal: React.FC = ({
} - placeholder="포트폴리오 이름을 입력해주세요 (최대 15글자)" + placeholder="이름을 입력해주세요 (최대 15글자)" size="medium" colorType="strokeType" value={portfolioName} diff --git a/src/Component/Modal/modal.tsx b/src/Component/Modal/modal.tsx index a83dbc1..015eeb6 100644 --- a/src/Component/Modal/modal.tsx +++ b/src/Component/Modal/modal.tsx @@ -1,8 +1,34 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import Button from "../Button/button"; import { ButtonStyle, ModalBody, ModalHeader, ModalStyles } from "./modalStyle"; import { ModalProps } from "../../constants/interface"; -import ReactModal from "react-modal"; +import ReactModal, { Styles } from "react-modal"; + +const getModalStyles = (isSmallScreen: boolean): Styles => { + return { + overlay: { + backgroundColor: "rgb(255 255 255 / 60%)", + width: "90%", + height: "100%", + zIndex: 100, + top: "0", + left: "0", + }, + content: { + width: isSmallScreen ? "85%" : "400px", + height: isSmallScreen ? "auto" : "450px", + zIndex: "11", + position: "fixed" as const, + scrollbarWidth: "none", + marginLeft: "-15px", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + overflow: "auto", + padding: isSmallScreen ? "20px" : "40px", + }, + }; +}; const ModalOpen: React.FC = ({ title, @@ -18,6 +44,32 @@ const ModalOpen: React.FC = ({ children, icon, }) => { + const [isSmallScreen, setIsSamllScreen] = useState( + window.matchMedia("(max-width: 768px)").matches + ); + + useEffect(() => { + const handleResize = () => { + setIsSamllScreen(window.matchMedia("(max-width: 768px)").matches); + }; + + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "auto"; + } + }); + + const modalStyles = getModalStyles(isSmallScreen); + return ( = ({ ariaHideApp={false} contentLabel={title || "모달창"} shouldCloseOnOverlayClick={false} - style={ModalStyles} + style={modalStyles} > {icon ? ( @@ -66,7 +118,7 @@ const ModalOpen: React.FC = ({ style={{ display: "flex", justifyContent: "center", - marginTop: "50px", + marginTop: isSmallScreen ? "20px" : "50px", }} > {showCancelButton && ( @@ -98,28 +150,3 @@ const ModalOpen: React.FC = ({ }; export default ModalOpen; - -// 모달 사용 예시 -{ - /* setIsOpen(false)} - showOneConfirmBtn={true} - text="자산 추가" - onConfirm={handleConfirm} - > -
-
-

APS

-

05462

-
-
-
수량
- -
단가
- -
-
-
*/ -} From 75b0c62965738b36b8571d6ce3252ea5f196308d Mon Sep 17 00:00:00 2001 From: "yeondu._" Date: Fri, 30 Aug 2024 11:02:58 +0900 Subject: [PATCH 5/6] =?UTF-8?q?Style=20:=20=EB=AA=A8=EB=8B=AC=EC=B0=BD=20?= =?UTF-8?q?=EB=AA=A8=EB=B0=94=EC=9D=BC=EB=B7=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Modal/modal.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Component/Modal/modal.tsx b/src/Component/Modal/modal.tsx index 015eeb6..88ca6cc 100644 --- a/src/Component/Modal/modal.tsx +++ b/src/Component/Modal/modal.tsx @@ -66,7 +66,10 @@ const ModalOpen: React.FC = ({ } else { document.body.style.overflow = "auto"; } - }); + return () => { + document.body.style.overflow = "auto"; + }; + }, [isOpen]); const modalStyles = getModalStyles(isSmallScreen); From 0c5cb056682447527a8d0b5e4445fb1e8fc7a1c2 Mon Sep 17 00:00:00 2001 From: yzooop Date: Fri, 30 Aug 2024 13:38:21 +0900 Subject: [PATCH 6/6] =?UTF-8?q?Fix=20:=20portfolio=20detail=20404=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Component/Carousel/EmblaCarousel.tsx | 393 ++++++++++---------- src/Component/Portfolio/PortfolioDetail.tsx | 2 +- 2 files changed, 201 insertions(+), 194 deletions(-) diff --git a/src/Component/Carousel/EmblaCarousel.tsx b/src/Component/Carousel/EmblaCarousel.tsx index c8f32cd..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,203 +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 0e8ac2a..a29be25 100644 --- a/src/Component/Portfolio/PortfolioDetail.tsx +++ b/src/Component/Portfolio/PortfolioDetail.tsx @@ -56,12 +56,12 @@ const PortfolioDetail = () => { ); if (res.status === 200) { - setChange(!change); swal({ title: "삭제 완료!", icon: "success", }).then(() => { nav("/portfolio"); + setChange(!change); }); setIsDeleteOpen(false); }