From 558490f97b9d2ae3e9437366c1c4124ae1699917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=91=ED=98=84?= Date: Fri, 15 Sep 2023 13:45:07 +0900 Subject: [PATCH] =?UTF-8?q?[Fea]=EC=A0=84=EC=B2=B4=EC=A2=85=EB=AA=A9,=20?= =?UTF-8?q?=EA=B4=80=EC=8B=AC=EC=A2=85=EB=AA=A9,=20=EB=B3=B4=EC=9C=A0?= =?UTF-8?q?=EC=A2=85=EB=AA=A9=20=ED=8F=B4=EB=8D=94=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EA=B0=81=EA=B0=81=EC=9D=98=20=ED=8F=B4=EB=8D=94=EC=97=90=20hea?= =?UTF-8?q?der,=20stockitem,=20list=20=ED=8C=8C=EC=9D=BC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A9=94=EB=89=B4=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=EC=8B=9C=20=EC=A0=84=EC=B2=B4=EC=A2=85?= =?UTF-8?q?=EB=AA=A9,=20=EA=B4=80=EC=8B=AC=EC=A2=85=EB=AA=A9,=20=EB=B3=B4?= =?UTF-8?q?=EC=9C=A0=EC=A2=85=EB=AA=A9=20=EB=B2=84=ED=8A=BC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20Issues=20#19?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{watchlist => EntireList}/EntireList.tsx | 4 +- client/src/components/EntireList/Header.tsx | 75 +++++++++++++++ .../src/components/EntireList/StockItem.tsx | 95 ++++++++++++++++++ client/src/components/HoldingList/Header.tsx | 75 +++++++++++++++ .../components/HoldingList/HoldingList.tsx | 96 +++++++++++++++++++ .../{watchlist => HoldingList}/Holdings.tsx | 13 +-- .../src/components/HoldingList/StockItem.tsx | 95 ++++++++++++++++++ client/src/components/watchlist/Header.tsx | 11 ++- client/src/components/watchlist/WatchList.tsx | 96 +++++++++++++++++++ client/src/page/MainPage.tsx | 18 ++-- 10 files changed, 560 insertions(+), 18 deletions(-) rename client/src/components/{watchlist => EntireList}/EntireList.tsx (94%) create mode 100644 client/src/components/EntireList/Header.tsx create mode 100644 client/src/components/EntireList/StockItem.tsx create mode 100644 client/src/components/HoldingList/Header.tsx create mode 100644 client/src/components/HoldingList/HoldingList.tsx rename client/src/components/{watchlist => HoldingList}/Holdings.tsx (91%) create mode 100644 client/src/components/HoldingList/StockItem.tsx create mode 100644 client/src/components/watchlist/WatchList.tsx diff --git a/client/src/components/watchlist/EntireList.tsx b/client/src/components/EntireList/EntireList.tsx similarity index 94% rename from client/src/components/watchlist/EntireList.tsx rename to client/src/components/EntireList/EntireList.tsx index bf5507c8..c4bad0aa 100644 --- a/client/src/components/watchlist/EntireList.tsx +++ b/client/src/components/EntireList/EntireList.tsx @@ -47,8 +47,8 @@ const WatchList: React.FC = ({ currentListType, onChangeListType // Props와 상태에 대한 타입 정의 type WatchListProps = { - currentListType: '관심목록' | '투자목록'; - onChangeListType: (type: '관심목록' | '투자목록') => void; + currentListType: '전체종목' | '관심종목' | '보유종목'; + onChangeListType: (type: '전체종목' | '관심종목' | '보유종목') => void; }; // WatchList 컴포넌트에 대한 스타일드 컴포넌트 정의 diff --git a/client/src/components/EntireList/Header.tsx b/client/src/components/EntireList/Header.tsx new file mode 100644 index 00000000..2f08c4dd --- /dev/null +++ b/client/src/components/EntireList/Header.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import styled from 'styled-components'; +import Menu_icon from "../../asset/images/menu.png"; + +const ALL_LIST = "전체종목"; +const INTEREST_LIST = "관심종목"; +const HOLDING_LIST = "보유종목"; + +const Header: React.FC = ({ currentListType, onChangeListType, isMenuOpen, setMenuOpen }) => { + return ( + + setMenuOpen(!isMenuOpen)} + /> + {currentListType} + {isMenuOpen && ( + + { onChangeListType(ALL_LIST); setMenuOpen(false); }}>{ALL_LIST} + { onChangeListType(INTEREST_LIST); setMenuOpen(false); }}>{INTEREST_LIST} + { onChangeListType(HOLDING_LIST); setMenuOpen(false); }}>{HOLDING_LIST} + + )} + + ); +}; + +type HeaderProps = { + currentListType: string; + onChangeListType: (type: "전체종목" | "관심종목" | "보유종목") => void; + isMenuOpen: boolean; + setMenuOpen: React.Dispatch>; +}; + + +const HeaderWrapper = styled.div` + display: flex; + align-items: center; + position: relative; +`; + +const Icon = styled.img` + margin-top: 9.5px; + width: 24px; + height: 24px; + cursor: pointer; + margin-right: 10px; +`; + +const HeaderText = styled.span` + margin-top: 9.5px; + 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; +`; + +export default Header; diff --git a/client/src/components/EntireList/StockItem.tsx b/client/src/components/EntireList/StockItem.tsx new file mode 100644 index 00000000..fade936a --- /dev/null +++ b/client/src/components/EntireList/StockItem.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import styled from 'styled-components'; +import logo from '../../asset/logos/SK_logo.png'; + +const StockItem: React.FC = ({ company, setShowChangePrice, showChangePrice }) => { + const isPositiveChange = parseFloat(company.stockChangeRate) > 0; + const priceColor = isPositiveChange ? 'red' : 'blue'; + + return ( + + + + {company.korName} + {company.code} + + + {company.stockPrice} + setShowChangePrice(true)} + onMouseLeave={() => setShowChangePrice(false)} + > + {showChangePrice ? `${company.stockChangeAmount}%` : `${company.stockChangeRate}%`} + + + + ); +}; + +type NewCompanyData = { + companyId: number; + code: string; + korName: string; + stockPrice: string; + stockChangeAmount: string; + stockChangeRate: string; +}; + +type StockItemProps = { + company: NewCompanyData; + setShowChangePrice: React.Dispatch>; + showChangePrice: boolean; +}; + +const StockItemWrapper = styled.div` + display: flex; + flex-direction: row; /* 수평으로 정렬 */ + justify-content: flex-start; /* 왼쪽 정렬 */ + align-items: flex-start; /* 위로 정렬 */ + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; + width: 100%; + background-color: transparent; + cursor: pointer; +`; + +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; + margin-left: auto; /* 자동으로 왼쪽 여백 추가 */ +`; + +const StockPrice = styled.span<{ change: string }>` + color: ${(props) => props.change}; +`; + +const StockChange = styled.span<{ change: string }>` + color: ${(props) => props.change}; + cursor: pointer; +`; + +export default StockItem; diff --git a/client/src/components/HoldingList/Header.tsx b/client/src/components/HoldingList/Header.tsx new file mode 100644 index 00000000..2f08c4dd --- /dev/null +++ b/client/src/components/HoldingList/Header.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import styled from 'styled-components'; +import Menu_icon from "../../asset/images/menu.png"; + +const ALL_LIST = "전체종목"; +const INTEREST_LIST = "관심종목"; +const HOLDING_LIST = "보유종목"; + +const Header: React.FC = ({ currentListType, onChangeListType, isMenuOpen, setMenuOpen }) => { + return ( + + setMenuOpen(!isMenuOpen)} + /> + {currentListType} + {isMenuOpen && ( + + { onChangeListType(ALL_LIST); setMenuOpen(false); }}>{ALL_LIST} + { onChangeListType(INTEREST_LIST); setMenuOpen(false); }}>{INTEREST_LIST} + { onChangeListType(HOLDING_LIST); setMenuOpen(false); }}>{HOLDING_LIST} + + )} + + ); +}; + +type HeaderProps = { + currentListType: string; + onChangeListType: (type: "전체종목" | "관심종목" | "보유종목") => void; + isMenuOpen: boolean; + setMenuOpen: React.Dispatch>; +}; + + +const HeaderWrapper = styled.div` + display: flex; + align-items: center; + position: relative; +`; + +const Icon = styled.img` + margin-top: 9.5px; + width: 24px; + height: 24px; + cursor: pointer; + margin-right: 10px; +`; + +const HeaderText = styled.span` + margin-top: 9.5px; + 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; +`; + +export default Header; diff --git a/client/src/components/HoldingList/HoldingList.tsx b/client/src/components/HoldingList/HoldingList.tsx new file mode 100644 index 00000000..c4bad0aa --- /dev/null +++ b/client/src/components/HoldingList/HoldingList.tsx @@ -0,0 +1,96 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import Header from './Header'; +import StockItem from './StockItem'; +import useCompanyData from '../../hooks/useCompanyData'; + +const WatchList: React.FC = ({ currentListType, onChangeListType }) => { + const [isMenuOpen, setMenuOpen] = useState(false); + const [showChangePrice, setShowChangePrice] = useState(false); + + // useCompanyData 훅 사용하여 데이터 가져오기 + const { data: companies, isLoading, isError } = useCompanyData(1, 14); + + // 'companies'가 'undefined'인 경우를 처리하기 위해 빈 배열로 초기화 + const companiesList = companies || []; + + return ( + +
+ + 평가 수익금: +5,000,000원 {/* 임의의 평가 수익금 */} + + + {isLoading ? ( +
Loading...
+ ) : isError ? ( +
Error fetching data
+ ) : ( + companiesList.map((company) => ( + + )) + )} +
+ + ); +}; + +// Props와 상태에 대한 타입 정의 +type WatchListProps = { + currentListType: '전체종목' | '관심종목' | '보유종목'; + onChangeListType: (type: '전체종목' | '관심종목' | '보유종목') => void; +}; + +// WatchList 컴포넌트에 대한 스타일드 컴포넌트 정의 +const WatchListContainer = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +const Divider1 = styled.div` +margin:0px; +padding:0px; +width: 100%; +height: 10px; +display: flex; +flex-direction: row; +border-bottom: 1px solid #2f4f4f; +`; + +const Divider2 = styled.div` +margin:0px; +padding:0px; +width: 100%; +height: 4.5px; +display: flex; +flex-direction: row; +border-bottom: 1px solid #2f4f4f; +`; + + + +const EvaluationProfit = styled.div` +font-size: 16px; +font-weight: bold; +margin: 8px 0; +text-align: center; +color: red; // 수익금이 플러스일 경우 초록색으로 표시 +`; +const StockList = styled.div` + width: 90%; + max-height: 800px; /* 스크롤이 발생할 최대 높이를 지정하세요 */ + overflow-y: auto; /* 세로 스크롤을 활성화합니다 */ +`; + +export default WatchList; diff --git a/client/src/components/watchlist/Holdings.tsx b/client/src/components/HoldingList/Holdings.tsx similarity index 91% rename from client/src/components/watchlist/Holdings.tsx rename to client/src/components/HoldingList/Holdings.tsx index 5f6f3857..5a03e2c8 100644 --- a/client/src/components/watchlist/Holdings.tsx +++ b/client/src/components/HoldingList/Holdings.tsx @@ -3,7 +3,7 @@ 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 Holdings: React.FC = ({ currentListType, onChangeListType }) => { const [isMenuOpen, setMenuOpen] = useState(false); @@ -28,8 +28,9 @@ const Holdings: React.FC = ({ currentListType, onChangeListType } {currentListType} {isMenuOpen && ( - { onChangeListType('관심목록'); setMenuOpen(false); }}>관심목록 - { onChangeListType('투자목록'); setMenuOpen(false); }}>투자목록 + { onChangeListType('관심종목'); setMenuOpen(false); }}>관심종목 + { onChangeListType('보유종목'); setMenuOpen(false); }}>투자종목 + { onChangeListType('전체종목'); setMenuOpen(false); }}>전체종목 )}
@@ -76,9 +77,9 @@ const Holdings: React.FC = ({ currentListType, onChangeListType } ); }; -type HoldingsProps = { - currentListType: "관심목록" | "투자목록"; - onChangeListType: (type: "관심목록" | "투자목록") => void; +type holdingsProps = { + currentListType: '전체종목' | '관심종목' | '보유종목'; + onChangeListType: (type: '전체종목' | '관심종목' | '보유종목') => void; }; const getColorByChange = (change: string) => { diff --git a/client/src/components/HoldingList/StockItem.tsx b/client/src/components/HoldingList/StockItem.tsx new file mode 100644 index 00000000..fade936a --- /dev/null +++ b/client/src/components/HoldingList/StockItem.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import styled from 'styled-components'; +import logo from '../../asset/logos/SK_logo.png'; + +const StockItem: React.FC = ({ company, setShowChangePrice, showChangePrice }) => { + const isPositiveChange = parseFloat(company.stockChangeRate) > 0; + const priceColor = isPositiveChange ? 'red' : 'blue'; + + return ( + + + + {company.korName} + {company.code} + + + {company.stockPrice} + setShowChangePrice(true)} + onMouseLeave={() => setShowChangePrice(false)} + > + {showChangePrice ? `${company.stockChangeAmount}%` : `${company.stockChangeRate}%`} + + + + ); +}; + +type NewCompanyData = { + companyId: number; + code: string; + korName: string; + stockPrice: string; + stockChangeAmount: string; + stockChangeRate: string; +}; + +type StockItemProps = { + company: NewCompanyData; + setShowChangePrice: React.Dispatch>; + showChangePrice: boolean; +}; + +const StockItemWrapper = styled.div` + display: flex; + flex-direction: row; /* 수평으로 정렬 */ + justify-content: flex-start; /* 왼쪽 정렬 */ + align-items: flex-start; /* 위로 정렬 */ + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; + width: 100%; + background-color: transparent; + cursor: pointer; +`; + +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; + margin-left: auto; /* 자동으로 왼쪽 여백 추가 */ +`; + +const StockPrice = styled.span<{ change: string }>` + color: ${(props) => props.change}; +`; + +const StockChange = styled.span<{ change: string }>` + color: ${(props) => props.change}; + cursor: pointer; +`; + +export default StockItem; diff --git a/client/src/components/watchlist/Header.tsx b/client/src/components/watchlist/Header.tsx index 883af6a3..2f08c4dd 100644 --- a/client/src/components/watchlist/Header.tsx +++ b/client/src/components/watchlist/Header.tsx @@ -2,8 +2,9 @@ import React from 'react'; import styled from 'styled-components'; import Menu_icon from "../../asset/images/menu.png"; -const INTEREST_LIST = "관심목록"; -const INVESTMENT_LIST = "투자목록"; +const ALL_LIST = "전체종목"; +const INTEREST_LIST = "관심종목"; +const HOLDING_LIST = "보유종목"; const Header: React.FC = ({ currentListType, onChangeListType, isMenuOpen, setMenuOpen }) => { return ( @@ -16,8 +17,9 @@ const Header: React.FC = ({ currentListType, onChangeListType, isMe {currentListType} {isMenuOpen && ( + { onChangeListType(ALL_LIST); setMenuOpen(false); }}>{ALL_LIST} { onChangeListType(INTEREST_LIST); setMenuOpen(false); }}>{INTEREST_LIST} - { onChangeListType(INVESTMENT_LIST); setMenuOpen(false); }}>{INVESTMENT_LIST} + { onChangeListType(HOLDING_LIST); setMenuOpen(false); }}>{HOLDING_LIST} )} @@ -26,11 +28,12 @@ const Header: React.FC = ({ currentListType, onChangeListType, isMe type HeaderProps = { currentListType: string; - onChangeListType: (type: "관심목록" | "투자목록") => void; + onChangeListType: (type: "전체종목" | "관심종목" | "보유종목") => void; isMenuOpen: boolean; setMenuOpen: React.Dispatch>; }; + const HeaderWrapper = styled.div` display: flex; align-items: center; diff --git a/client/src/components/watchlist/WatchList.tsx b/client/src/components/watchlist/WatchList.tsx new file mode 100644 index 00000000..c4bad0aa --- /dev/null +++ b/client/src/components/watchlist/WatchList.tsx @@ -0,0 +1,96 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import Header from './Header'; +import StockItem from './StockItem'; +import useCompanyData from '../../hooks/useCompanyData'; + +const WatchList: React.FC = ({ currentListType, onChangeListType }) => { + const [isMenuOpen, setMenuOpen] = useState(false); + const [showChangePrice, setShowChangePrice] = useState(false); + + // useCompanyData 훅 사용하여 데이터 가져오기 + const { data: companies, isLoading, isError } = useCompanyData(1, 14); + + // 'companies'가 'undefined'인 경우를 처리하기 위해 빈 배열로 초기화 + const companiesList = companies || []; + + return ( + +
+ + 평가 수익금: +5,000,000원 {/* 임의의 평가 수익금 */} + + + {isLoading ? ( +
Loading...
+ ) : isError ? ( +
Error fetching data
+ ) : ( + companiesList.map((company) => ( + + )) + )} +
+ + ); +}; + +// Props와 상태에 대한 타입 정의 +type WatchListProps = { + currentListType: '전체종목' | '관심종목' | '보유종목'; + onChangeListType: (type: '전체종목' | '관심종목' | '보유종목') => void; +}; + +// WatchList 컴포넌트에 대한 스타일드 컴포넌트 정의 +const WatchListContainer = styled.div` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +const Divider1 = styled.div` +margin:0px; +padding:0px; +width: 100%; +height: 10px; +display: flex; +flex-direction: row; +border-bottom: 1px solid #2f4f4f; +`; + +const Divider2 = styled.div` +margin:0px; +padding:0px; +width: 100%; +height: 4.5px; +display: flex; +flex-direction: row; +border-bottom: 1px solid #2f4f4f; +`; + + + +const EvaluationProfit = styled.div` +font-size: 16px; +font-weight: bold; +margin: 8px 0; +text-align: center; +color: red; // 수익금이 플러스일 경우 초록색으로 표시 +`; +const StockList = styled.div` + width: 90%; + max-height: 800px; /* 스크롤이 발생할 최대 높이를 지정하세요 */ + overflow-y: auto; /* 세로 스크롤을 활성화합니다 */ +`; + +export default WatchList; diff --git a/client/src/page/MainPage.tsx b/client/src/page/MainPage.tsx index 46f4b927..120d9489 100644 --- a/client/src/page/MainPage.tsx +++ b/client/src/page/MainPage.tsx @@ -10,8 +10,8 @@ import EmailSignupModal from "../components/Signups/EmailSignup"; import EmailVerificationModal from "../components/Signups/EmailCertify"; import PasswordSettingModal from "../components/Signups/Password"; import CentralChart from "../components/CentralChart/Index"; -import WatchList from "../components/watchlist/EntireList"; -import Holdings from "../components/watchlist/Holdings"; // Assuming you have a Holdings component +import WatchList from "../components/EntireList/EntireList"; +import Holdings from "../components/HoldingList/Holdings"; // Assuming you have a Holdings component import CompareChartSection from "../components/CompareChartSection/Index"; import StockOrderSection from "../components/StockOrderSection/Index"; import Welcome from "../components/Signups/Welcome"; @@ -127,9 +127,9 @@ const MainPage = () => { setLoginConfirmationModalOpen(false); }; - const [selectedMenu, setSelectedMenu] = useState<"관심목록" | "투자목록">("투자목록"); // Default menu is 관심목록 + const [selectedMenu, setSelectedMenu] = useState<"전체종목" | "관심종목" | "보유종목">("전체종목"); - const handleMenuChange = (menu: "관심목록" | "투자목록") => { + const handleMenuChange = (menu: "전체종목" | "관심종목" | "보유종목") => { setSelectedMenu(menu); }; @@ -140,14 +140,20 @@ const MainPage = () => {
{!expandScreen.left && ( - {selectedMenu === "관심목록" ? : } + + {selectedMenu === "전체종목" ? ( + + ) : ( + + )} + )} {!expandScreen.right && }
{isOAuthModalOpen && ( - handleMenuChange("관심목록")} onHoldingsClick={() => handleMenuChange("투자목록")} /> + handleMenuChange("관심종목")} onHoldingsClick={() => handleMenuChange("보유종목")} /> )} {isEmailLoginModalOpen && }