diff --git a/client/src/asset/images/menu.png b/client/src/asset/images/menu.png new file mode 100644 index 00000000..06afba7a Binary files /dev/null and b/client/src/asset/images/menu.png differ diff --git a/client/src/asset/logos/LG_logo.svg b/client/src/asset/logos/LG_logo.svg new file mode 100644 index 00000000..28ef44b3 --- /dev/null +++ b/client/src/asset/logos/LG_logo.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/client/src/asset/logos/POSCO_logo.svg b/client/src/asset/logos/POSCO_logo.svg new file mode 100644 index 00000000..ae93452e --- /dev/null +++ b/client/src/asset/logos/POSCO_logo.svg @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/client/src/asset/logos/SK_logo.png b/client/src/asset/logos/SK_logo.png new file mode 100644 index 00000000..701c350d Binary files /dev/null and b/client/src/asset/logos/SK_logo.png differ diff --git a/client/src/asset/logos/Samsung_logo.svg b/client/src/asset/logos/Samsung_logo.svg new file mode 100644 index 00000000..06f0f3e1 --- /dev/null +++ b/client/src/asset/logos/Samsung_logo.svg @@ -0,0 +1,99 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/client/src/components/Logins/OAuthLogin.tsx b/client/src/components/Logins/OAuthLogin.tsx index 665385ee..78e1b1c2 100644 --- a/client/src/components/Logins/OAuthLogin.tsx +++ b/client/src/components/Logins/OAuthLogin.tsx @@ -74,6 +74,8 @@ interface LoginModalProps { onClose: () => void; onEmailLoginClick: () => void; onEmailSignupClick: () => void; // 추가 + onWatchListClick: () => void; // 추가 + onHoldingsClick: () => void; // 추가 } const OrText = styled.span` diff --git a/client/src/components/watchlist/Holdings.tsx b/client/src/components/watchlist/Holdings.tsx new file mode 100644 index 00000000..6c51b0cd --- /dev/null +++ b/client/src/components/watchlist/Holdings.tsx @@ -0,0 +1,246 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import Samsung_logo from "../../asset/logos/Samsung_logo.svg" +import Menu_icon from "../../asset/images/menu.png" + +const Holdings: React.FC = ({ currentListType, onChangeListType }) => { + const [isMenuOpen, setMenuOpen] = useState(false); + + + const holdingsData = [ + { name: "삼성전자", code: "005930", price: "71,000원", change: "+6.13%", + profit: "수익", holding: "보유", profitAmount: "+10,000원", purchasePrice: "61,000원", + rateOfReturn: "+15%", stocksHeld: "100주", logo: Samsung_logo + }, + // ... (다른 종목들의 더미 데이터도 추가 가능) + ]; + + const [showChangePrice, setShowChangePrice] = useState(false); + + return ( + +
+ setMenuOpen(!isMenuOpen)} + /> + {currentListType} + {isMenuOpen && ( + + { onChangeListType('관심목록'); setMenuOpen(false); }}>관심목록 + { onChangeListType('투자목록'); setMenuOpen(false); }}>투자목록 + + )} +
+ + 평가 수익금: +5,000,000원 {/* 임의의 평가 수익금 */} + + {holdingsData.map(stock => ( + <> + + + + {stock.name} + {stock.code} + + + {stock.price} + setShowChangePrice(true)} + onMouseLeave={() => setShowChangePrice(false)} + > + {showChangePrice ? stock.profitAmount : stock.change} + + + + + + {stock.profit} + {stock.holding} + + + {stock.profitAmount} + {stock.purchasePrice} + + + {stock.rateOfReturn} + {stock.stocksHeld} + + + + + ))} +
+ ); +}; + +type HoldingsProps = { + currentListType: "관심목록" | "투자목록"; + onChangeListType: (type: "관심목록" | "투자목록") => void; +}; + +const getColorByChange = (change: string) => { + if (change.startsWith('+')) return 'red'; + if (change.startsWith('-')) return 'blue'; + return 'black'; + }; + + const HoldingsContainer = styled.div` + padding: 16px; + `; + + const Header = styled.div` + display: flex; + align-items: center; + position: relative; + `; + + const Icon = styled.img` + width: 24px; + height: 24px; + cursor: pointer; + margin-right: 10px; + `; + + const HeaderText = styled.span` + font-size: 18px; + `; + + const SlideMenu = styled.div` + position: absolute; + top: 100%; + left: 0; + width: 248px; + background-color: #f7f7f7; + border: 1px solid #e0e0e0; + display: flex; + flex-direction: column; + `; + + const MenuItem = styled.button` + padding: 8px 16px; + border: none; + background-color: transparent; + cursor: pointer; + text-align: left; + + &:hover { + background-color: #e0e0e0; + } + `; + + const StockItem = styled.button` + display: flex; + justify-content: space-between; + align-items: flex-start; // 시작 위치 정렬 추가 + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; + width: 100%; + background-color: transparent; + cursor: pointer; + border: none; + text-align: left; + `; + + const Logo = styled.img` + border-radius: 50%; + width: 40px; + height: 40px; + margin-right: 12px; + `; + + const StockInfo = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; // 시작 위치 정렬 추가 + margin-right: 16px; // 간격 추가 + `; + + const StockName = styled.span` + font-weight: bold; + `; + + const StockCode = styled.span` + color: gray; + `; + + const StockPriceSection = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; // 시작 위치 정렬 추가 + `; + + const StockPrice = styled.span.attrs<{ change: string }>(({ change }) => ({ + style: { + color: getColorByChange(change), + }, + }))``; + + const StockChange = styled.span.attrs<{ change: string }>(({ change }) => ({ + style: { + color: getColorByChange(change), + }, + }))` + cursor: pointer; // 마우스 포인터 변경 추가 + `; + + const Divider = styled.div` + height: 1px; + background-color: #aaa; // 회색으로 설정 + margin: 8px 0; // 상하 여백 추가 + `; + + +const EvaluationProfit = styled.div` + font-size: 16px; + font-weight: bold; + margin: 10px 0; + text-align: center; + color: red; // 수익금이 플러스일 경우 초록색으로 표시 +`; + +const StockDetails = styled.div` + display: flex; + justify-content: space-between; + padding: 8px 0; + width: 100%; +`; + +const DetailSection = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; + +const DetailTitle = styled.span` + font-weight: light; + font-size : 14px; +`; + +const DetailData = styled.span` + font-size: 14px; // Setting standardized font size for all data +`; + +const getColorByValue = (value: string) => { + if (value.startsWith('+')) return 'red'; + if (value.startsWith('-')) return 'blue'; + return 'black'; +}; + +const ColoredDetailData = styled.span.attrs<{ value: string }>(({ value }) => ({ + style: { + color: getColorByValue(value), + }, +}))` + font-size: 14px; // Setting standardized font size for all data +`; + +const ThickDivider = styled.div` + height: 2px; + background-color: #aaa; + margin: 8px 0; +`; + +export default Holdings; diff --git a/client/src/components/watchlist/WatchList.tsx b/client/src/components/watchlist/WatchList.tsx new file mode 100644 index 00000000..5fcee4b4 --- /dev/null +++ b/client/src/components/watchlist/WatchList.tsx @@ -0,0 +1,197 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import Samsung_logo from "../../asset/logos/Samsung_logo.svg" +import LG_logo from "../../asset/logos/LG_logo.svg" +import Sk_logo from "../../asset/logos/Sk_logo.png" +import POSCO_logo from "../../asset/logos/POSCO_logo.svg" +import Menu_icon from "../../asset/images/menu.png" + +const WatchList: React.FC = ({ currentListType, onChangeListType }) => { + + const [isMenuOpen, setMenuOpen] = useState(false); + + + const favoriteStocks = [ + { name: "삼성전자", code: "005930", price: "71,000원", change: "+6.13%", changePrice: "+4,100원", logo: Samsung_logo }, + { name: "LG에너지솔루션", code: "373220", price: "522,000원", change: "-4.04%", changePrice: "-22,000원", logo: LG_logo }, + { name: "SK하이닉스", code: "000660", price: "120,000원", change: "-1.48%", changePrice: "-1,800원", logo: Sk_logo }, + { name: "삼성바이오로직스", code: "207940", price: "733,000원", change: "-0.54%", changePrice: "-4,000원", logo: Samsung_logo }, + { name: "POSCO홀딩스", code: "005490", price: "560,000원", change: "-3.28%", changePrice: "-19,000원", logo: POSCO_logo }, + { name: "삼성전자우", code: "005935", price: "56,900원", change: "+5.37%", changePrice: "+2,900원", logo: Samsung_logo }, + { name: "삼성SDI", code: "006400", price: "596,000원", change: "-2.93%", changePrice: "-18,000원", logo: Samsung_logo } + ]; + + const [showChangePrice, setShowChangePrice] = useState(false); + + return ( + +
+ setMenuOpen(!isMenuOpen)} + /> + {currentListType} + {isMenuOpen && ( + + { onChangeListType('관심목록'); setMenuOpen(false); }}>관심목록 + { onChangeListType('투자목록'); setMenuOpen(false); }}>투자목록 + + )} +
+ + { /* 종목 추가 로직 */ }}>종목 추가 + + {favoriteStocks.map(stock => ( + + + + {stock.name} + {stock.code} + + + {stock.price} + setShowChangePrice(true)} + onMouseLeave={() => setShowChangePrice(false)} + > + {showChangePrice ? stock.changePrice : stock.change} + + + + ))} +
+ ); +}; + +type WatchListProps = { + currentListType: "관심목록" | "투자목록"; + onChangeListType: (type: "관심목록" | "투자목록") => void; +}; + +const getColorByChange = (change: string) => { + if (change.startsWith('+')) return 'red'; + if (change.startsWith('-')) return 'blue'; + return 'black'; +}; + +const WatchListContainer = styled.div` + padding: 16px; +`; + +const Header = styled.div` + display: flex; + align-items: center; + position: relative; +`; + +const Icon = styled.img` + width: 24px; // 너비를 설정합니다. 원하는 크기로 조절 가능합니다. + height: 24px; // 높이를 설정합니다. 원하는 크기로 조절 가능합니다. + cursor: pointer; + margin-right: 10px; +`; + +const HeaderText = styled.span` + font-size: 18px; +`; + +const SlideMenu = styled.div` + position: absolute; + top: 100%; + left: 0; + width: 248px; + background-color: #f7f7f7; + border: 1px solid #e0e0e0; + display: flex; + flex-direction: column; +`; + +const MenuItem = styled.button` + padding: 8px 16px; + border: none; + background-color: transparent; + cursor: pointer; + text-align: left; + + &:hover { + background-color: #e0e0e0; + } +`; + +const StockItem = styled.button` + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; + width: 100%; + background-color: transparent; + cursor: pointer; + border: none; + text-align: left; +`; + +const Logo = styled.img` + border-radius: 50%; + width: 40px; + height: 40px; + margin-right: 12px; +`; + +const StockInfo = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; + margin-right: 16px; +`; + +const StockName = styled.span` + font-weight: bold; +`; + +const StockCode = styled.span` + color: gray; +`; + +const StockPriceSection = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +const StockPrice = styled.span.attrs<{ change: string }>(({ change }) => ({ + style: { + color: getColorByChange(change), + }, +}))``; + +const StockChange = styled.span.attrs<{ change: string }>(({ change }) => ({ + style: { + color: getColorByChange(change), + }, + }))` + cursor: pointer; + `; + +const Divider = styled.div` + height: 1px; + background-color: #aaa; + margin: 8px 0; +`; + +const AddStockButton = styled.button` + padding: 10px 20px; + border: none; + background-color: transparent; + cursor: pointer; + text-align: left; + color: black; + + &:hover { + background-color: #e0e0e0; + } +`; + +export default WatchList; diff --git a/client/src/page/MainPage.tsx b/client/src/page/MainPage.tsx index 59c868c0..b342917d 100644 --- a/client/src/page/MainPage.tsx +++ b/client/src/page/MainPage.tsx @@ -7,8 +7,9 @@ import EmailLoginModal from "../components/Logins/EmailLogin"; import EmailSignupModal from "../components/Signups/EmailSignup"; import EmailVerificationModal from "../components/Signups/EmailCertify"; import PasswordSettingModal from "../components/Signups/Password"; - import CentralChartSection from "../components/CentralChartSection/Index"; +import WatchList from "../components/watchlist/WatchList"; +import Holdings from "../components/watchlist/Holdings"; // Assuming you have a Holdings component import CompareChartSection from "../components/CompareChartSection/Index"; import StockOrderSection from "../components/StockOrderSection/Index"; @@ -71,6 +72,12 @@ const MainPage = () => { setIsLoggedIn(true); }; + const [selectedMenu, setSelectedMenu] = useState<"관심목록" | "투자목록">("투자목록"); // Default menu is 관심목록 + + const handleMenuChange = (menu: "관심목록" | "투자목록") => { + setSelectedMenu(menu); + }; + const handleLogout = () => { setIsLoggedIn(false); }; @@ -78,39 +85,57 @@ const MainPage = () => { return ( {isLoggedIn ? ( - // 로그아웃 버튼 클릭 핸들러 추가 + ) : ( )}
- + + {selectedMenu === "관심목록" ? ( + + ) : ( + + )} +
- {isOAuthModalOpen && } + {isOAuthModalOpen && ( + handleMenuChange("관심목록")} + onHoldingsClick={() => handleMenuChange("투자목록")} + /> + )} {isEmailLoginModalOpen && ( )} - {isEmailSignupModalOpen && ( )} - {isEmailVerificationModalOpen && ( )} - {isPasswordSettingModalOpen && ( { @@ -150,4 +175,4 @@ export const RightSection = styled.section` min-width: 400px; height: 100%; border: 1px solid black; -`; +`; \ No newline at end of file