Skip to content

Commit

Permalink
[Feat] 미체결 거래 체결여부 확인 웹소켓 연결 및 기타 수정
Browse files Browse the repository at this point in the history
- 미체결 거래 체결 여부를 조회할 수 있도록 웹소켓 연결 구현
- 거래가능 시간 외 주문불가 및 차트갱신 로직 구동되지 않도록 설정
- 텍스트 관련 오타 수정

Issues #122
  • Loading branch information
novice1993 committed Sep 19, 2023
1 parent 90d50cd commit ad5ed34
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 97 deletions.
8 changes: 7 additions & 1 deletion client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"react-spinners": "^0.13.8",
"react-toastify": "^9.1.3",
"redux-persist": "^6.0.0",
"styled-components": "^6.0.7"
"styled-components": "^6.0.7",
"webstomp-client": "^1.2.6"
},
"devDependencies": {
"@types/node": "^20.6.0",
Expand Down
3 changes: 2 additions & 1 deletion client/src/components/CentralChartMenu/ExpandScreenBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ interface OwnProps {

// component 생성
const Button = styled.div<OwnProps>`
width: ${(props) => (props.buttonHover ? "46px" : "8px")};
width: ${(props) => (props.buttonHover ? "46px" : "6px")};
/* width: 46px; */
height: 100%;
display: flex;
justify-content: center;
Expand Down
19 changes: 14 additions & 5 deletions client/src/components/CentralChartMenu/Index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import { useSelector } from "react-redux";
import { styled } from "styled-components";
import useGetStockInfo from "../../hooks/useGetStockInfo";
import { StateProps } from "../../models/stateProps";
import ExpandScreenBtn from "./ExpandScreenBtn";
import StockOverview from "./StockOverview";
import StockOrderBtn from "./StockOrderBtn";
// import CompareChartBtn from "./CompareChartBtn";

const UpperMenuBar = () => {
const companyId = useSelector((state: StateProps) => state.companyId);
const { stockInfoLoading, stockInfoError } = useGetStockInfo(companyId);

if (stockInfoLoading) {
return <></>;
}

if (stockInfoError) {
return <p>에러 발생</p>;
}

return (
<Container>
<div className="FirstLine">
<ExpandScreenBtn direction="left" />
<StockOverview />
{/* <BookmarkBtn /> */}
<StockOrderBtn type="buying" />
<StockOrderBtn type="selling" />
<ExpandScreenBtn direction="right" />
</div>
{/* <div className="SecondLine">
<CompareChartBtn />
</div> */}
</Container>
);
};
Expand Down
14 changes: 7 additions & 7 deletions client/src/components/CentralChartMenu/StockOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const valueText: string = "거래대금";
// styled-component 수정 및 미디어 쿼리 적용
const StockOverview = () => {
const companyId = useSelector((state: StateProps) => state.companyId);
const { stockInfo, stockInfoLoading, stockInfoError } = useGetStockInfo(companyId);
const { stockInfo } = useGetStockInfo(companyId);

const corpName = stockInfo?.korName;

Expand All @@ -54,13 +54,13 @@ const StockOverview = () => {
return null; // 혹은 다른 적절한 렌더링을 반환
}

if (stockInfoLoading) {
return <p>로딩 중 입니다</p>;
}
// if (stockInfoLoading) {
// return <></>;
// }

if (stockInfoError) {
return <p>에러 발생</p>;
}
// if (stockInfoError) {
// return <p>에러 발생</p>;
// }

const stockCode = stockInfo.code;
const stockPrice = parseInt(stockInfo.stockInfResponseDto.stck_prpr, 10).toLocaleString();
Expand Down
17 changes: 10 additions & 7 deletions client/src/components/StockOrderSection/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const StockOrderSection: React.FC<StockOrderSectionProps> = (props) => {

// 1) 데이터 로딩 중
if (isLoading) {
return <Container orderSet={stockOrderSet}>로딩 중</Container>;
return <Container orderSet={stockOrderSet}></Container>;
}

// 주식주문 창 닫기
Expand Down Expand Up @@ -149,7 +149,7 @@ const StockOrderSection: React.FC<StockOrderSectionProps> = (props) => {
<WaitOrderIndicator />
</div>
) : (
<LoginRequestIndicator openOAuthModal={props.openOAuthModal} /> //props전달
<LoginRequestIndicator openOAuthModal={props.openOAuthModal} /> //props전달
)}
</Container>
);
Expand All @@ -159,17 +159,18 @@ export default StockOrderSection;

interface StockOrderSectionProps {
openOAuthModal: () => void;
openProfileModal: () => void; // Add this line
openProfileModal: () => void; // Add this line
}


// 미로그인 시 -> 로그인 요청 화면
//props 전달
const LoginRequestIndicator: React.FC<LoginRequestIndicatorProps> = ({ openOAuthModal }) => {
return (
<LoginRequestContainer>
<div className="Notification">{loginRequiredText}</div>
<button className="LoginButton" onClick={openOAuthModal}>{loginBtnText}</button>
<button className="LoginButton" onClick={openOAuthModal}>
{loginBtnText}
</button>
</LoginRequestContainer>
);
};
Expand All @@ -183,7 +184,9 @@ const MoneyReqireIndicator: React.FC<MoneyReqireIndicatorProps> = ({ openProfile
return (
<MoneyRequireContainer>
<div className="Notification">{moneyRequireText}</div>
<button className="LoginButton" onClick={openProfileModal}>{moenyRequireBtnText}</button>
<button className="LoginButton" onClick={openProfileModal}>
{moenyRequireBtnText}
</button>
</MoneyRequireContainer>
);
};
Expand Down Expand Up @@ -285,7 +288,7 @@ const LoginRequestContainer = styled.div`
background-color: #2f4f4f;
border: none;
border-radius: 0.3rem;
cursor: pointer;
cursor: pointer;
}
`;

Expand Down
20 changes: 10 additions & 10 deletions client/src/components/StockOrderSection/StockOrder.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSelector, useDispatch } from "react-redux";
// import { isHoliday } from "@hyunbinseo/holidays-kr";
import { isHoliday } from "@hyunbinseo/holidays-kr";
import { setStockOrderVolume } from "../../reducer/StockOrderVolume-Reducer";
import { closeDecisionWindow } from "../../reducer/SetDecisionWindow-Reducer";
import { styled } from "styled-components";
Expand Down Expand Up @@ -123,22 +123,22 @@ const StockOrder = ({ corpName }: { corpName: string }) => {
};

// 1) 주말, 공휴일 여부 체크
// const today = new Date();
// const nonBusinessDay = isHoliday(today, { include: { saturday: true, sunday: true } }); // 토요일, 일요일, 공휴일 (임시 공휴일 포함)
const today = new Date();
const nonBusinessDay = isHoliday(today, { include: { saturday: true, sunday: true } }); // 토요일, 일요일, 공휴일 (임시 공휴일 포함)

// 🟢 2) 개장시간 여부 체크
// const currentHour = today.getHours();
// const currentMinute = today.getMinutes();
// const isBefore9AM = currentHour < 9;
// const isAfter330PM = currentHour > 15 || (currentHour === 15 && currentMinute >= 30);
// const closingTime = isBefore9AM || isAfter330PM;
const currentHour = today.getHours();
const currentMinute = today.getMinutes();
const isBefore9AM = currentHour < 9;
const isAfter330PM = currentHour > 15 || (currentHour === 15 && currentMinute >= 30);
const closingTime = isBefore9AM || isAfter330PM;

// 주문 실패 케이스 1) 개장시간 2) 가격/거래량 설정
// 🔴 3시 30분 이후 작업 위해 closingTime 조건 해제 + 주말 요건도 해제
const orderFailureCase01 = false;
// const orderFailureCase01 = false;

// 🟢 기존로직
// const orderFailureCase01 = nonBusinessDay || closingTime;
const orderFailureCase01 = nonBusinessDay || closingTime;
const orderFailureCase02 = orderPrice === 0 || orderVolume === 0;

return (
Expand Down
134 changes: 117 additions & 17 deletions client/src/components/StockOrderSection/WaitOrderIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,131 @@
import { useEffect } from "react";
import { styled } from "styled-components";
import { toast } from "react-toastify";
import { useState, useEffect } from "react";
import useGetMemberId from "../../hooks/useGetMemberId";
import * as Webstomp from "webstomp-client";

import useGetWaitOrderSuccessInfo from "../../hooks/useGetWaitOrderSuccessInfo";
const url = "ws://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/ws";

const WaitOrderIndicator = () => {
const { waitOrderSuccessData } = useGetWaitOrderSuccessInfo();
//기업로고 import
import kia from "../../asset/logos/기아.svg";
import dy from "../../asset/logos/디와이.jpeg";
import logosamsung from "../../asset/logos/삼성전자.svg";
import celltrion from "../../asset/logos/셀트리온.svg";
import ecopro from "../../asset/logos/에코프로.jpeg";
import ecoproBM from "../../asset/logos/에코프로비엠.svg";
import kakaoBank from "../../asset/logos/카카오뱅크.svg";
import kuckoo from "../../asset/logos/쿠쿠홀딩스.jpeg";
import hanse from "../../asset/logos/한세엠케이.jpeg";
import hyundai from "../../asset/logos/현대차.svg";
import KG from "../../asset/logos/KG케미칼.png";
import LGelec from "../../asset/logos/LG전자.svg";
import LGchem from "../../asset/logos/LG화학.svg";
import posco from "../../asset/logos/POSCO홀딩스.svg";

const corpLogo = [logosamsung, posco, celltrion, ecopro, ecoproBM, dy, kuckoo, kakaoBank, hanse, KG, LGchem, hyundai, LGelec, kia];
const corpName = ["삼성전자", "POSCO홀딩스", "셀트리온", "에코프로", "에코프로비엠", "디와이", "쿠쿠홀딩스", "카카오뱅크", "한세엠케이", "KG케마칼", "LG화학", "현대차", "LG전자", "기아"];

const toastStyle = {
fontSize: "15px",
fontWeight: 450,
color: "#2679ed",
};
const volumeUnit = "주";
const toastText01: string = "미체결";
const toastText02: string = " 체결 처리 되었습니다";

const WaitOrderIndicator = () => {
const { memberId } = useGetMemberId();
const [waitOrder, setWaitOrder] = useState<WaitOrderProps | null>(null);

useEffect(() => {
if (waitOrderSuccessData) {
console.log(waitOrderSuccessData);
if (memberId) {
const socket = new WebSocket(url);
const stompClient = Webstomp.over(socket);

if (waitOrderSuccessData[0].length !== 0 && waitOrderSuccessData[1].length !== 0) {
toast.info(`대기주문이 체결되었습니다`, {
style: toastStyle,
position: "bottom-left",
const headers = {
memberId: memberId,
};

stompClient.connect(headers, () => {
console.log("Connected with headers:", headers);
console.log("Connected to the WebSocket server");

stompClient.subscribe(`/sub/${memberId}`, async (data) => {
const responseData = JSON.parse(data.body);
const orderType = responseData.orderTypes === "BUY" ? "매수" : "매도";
const orderVolume = responseData.stockCount;
const companyId = responseData.companyId;
const result = { companyId: companyId, orderType: orderType, orderVolume: orderVolume };
setWaitOrder(result);
});
}
});
}
}, [memberId]);

useEffect(() => {
if (waitOrder !== null) {
const companyId = waitOrder.companyId;
const type = waitOrder.orderType;
const logo = corpLogo[companyId - 1];
const name = corpName[companyId - 1];
const volume = waitOrder.orderVolume;

toast(
<ToastMessage orderType={type}>
<div className="overview">
<img src={logo} alt="stock logo" />
<div className="orderInfo">
{name} {volume}
{volumeUnit}
</div>
</div>
<div>
<span className="orderType">
{toastText01} {type}
</span>
<span>{toastText02}</span>
</div>
</ToastMessage>,
{
position: toast.POSITION.BOTTOM_LEFT,
// autoClose: 2000,
hideProgressBar: true,
}
);
}
}, [waitOrderSuccessData]);
}, [waitOrder]);

return <div></div>;
};

export default WaitOrderIndicator;

interface WaitOrderProps {
companyId: number;
orderType: string;
orderVolume: number;
}

const ToastMessage = styled.div<{ orderType: string }>`
display: flex;
flex-direction: column;
gap: 7px;
font-size: 14px;
.overview {
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
font-weight: 700;
gap: 6px;
}
& img {
width: 24px;
height: 24px;
border-radius: 50%;
padding-bottom: 3px;
}
.orderType {
color: ${(props) => (props.orderType === "매수" ? "#e22926" : "#2679ed")};
}
`;
2 changes: 1 addition & 1 deletion client/src/components/communityComponents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ const TimeLineComponent = () => {

<ButtonContainer>
<SubmitButton onClick={handleClickSubmit}>Submit</SubmitButton>
<CloseButton onClick={handleSetOpenDropDown}>Cancle</CloseButton>
<CloseButton onClick={handleSetOpenDropDown}>Cancel</CloseButton>
</ButtonContainer>
</>
)}
Expand Down
31 changes: 31 additions & 0 deletions client/src/hooks/useGetMemberId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useSelector } from "react-redux";
import { useQuery } from "react-query";
import { StateProps } from "../models/stateProps";
import axios from "axios";

const url = "http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/members";

const useGetMemberId = () => {
const login = useSelector((state: StateProps) => state.login);
const { data, isLoading, isError } = useQuery("memberId", getMemberId, {
enabled: login === 1,
});

return { memberId: data, memberIdLoading: isLoading, memberIdError: isError };
};

export default useGetMemberId;

const getMemberId = async () => {
const accessToken = localStorage.getItem("accessToken");
const options = {
headers: {
Authorization: `${accessToken}`,
"Content-Type": "application/json",
},
};

const respone = await axios.get(url, options);
const memberId = await respone.data.memberId;
return memberId;
};
Loading

0 comments on commit ad5ed34

Please sign in to comment.