Skip to content

Commit

Permalink
[Feat] 미체결 주식 관련 주문취소 기능 구현
Browse files Browse the repository at this point in the history
- 거래시점에 거래량 존재하지 않아 미체결로 분류된 주문 관련하여 주문 취소 기능 구현
- 취소 의사 이중 확인을 위한 모달창 필요한 상황이나, BE 담당자와 API 관련 추가 논의 후 확정되면 추가로 구현할 예정임

Issues #17
  • Loading branch information
novice1993 committed Sep 13, 2023
1 parent 91c32ad commit 3838840
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 63 deletions.
22 changes: 15 additions & 7 deletions client/src/components/StockOrderSection/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ import useGetStockData from "../../hooks/useGetStockData";
import useGetCash from "../../hooks/useGetCash";
import useGetStockOrderRecord from "../../hooks/useGetStockOrderRecord";
import useGetHoldingStock from "../../hooks/useGetHoldingStock";
import useGetCompanyList from "../../hooks/useGetCompanyList";
import { stockOrderClose } from "../../reducer/StockOrderSet-Reducer";
import { StateProps } from "../../models/stateProps";
import StockOrder from "./StockOrder";
import OrderResult from "./OrderResult";

const errorMessage: string = "정보를 불러올 수 없습니다";
const errorButtonText: string = "닫기";

const loginRequiredText: string = "로그인이 필요한 서비스입니다";
const loginBtnText: string = "StockHolm 로그인";

const upperbarTitle: string = "주식주문";
const marketType: string = "코스피";

Expand All @@ -33,10 +32,11 @@ const StockOrderSection = () => {
const { cashLoading, cashError } = useGetCash();
const { orderRecordLoading, orderRecordError } = useGetStockOrderRecord();
const { holdingStockLoading, holdingStockError } = useGetHoldingStock();
const { compnayListLoading, companyListError } = useGetCompanyList();

// fetching 데이터 loading, error 여부
const isLoading = stockInfoLoading || stockPriceLoading || cashLoading || orderRecordLoading || holdingStockLoading;
const isError = stockInfoError || stockPriceError || cashError || orderRecordError || holdingStockError;
const isLoading = stockInfoLoading || stockPriceLoading || cashLoading || orderRecordLoading || holdingStockLoading || compnayListLoading;
const isError = stockInfoError || stockPriceError || cashError || orderRecordError || holdingStockError || companyListError;

// 1) 데이터 로딩 중
if (isLoading) {
Expand Down Expand Up @@ -75,7 +75,7 @@ const StockOrderSection = () => {
</button>
</UpperBar>
{isLogin === 1 ? (
<>
<div className="mainContent">
<StockName>
<img className="CorpLogo" src={dummyLogo} />
<div className="NameContainer">
Expand All @@ -87,7 +87,7 @@ const StockOrderSection = () => {
</StockName>
<StockOrder corpName={corpName} />
<OrderResult />
</>
</div>
) : (
<LoginRequestIndicator />
)}
Expand Down Expand Up @@ -119,9 +119,17 @@ const Container = styled.aside<{ orderSet: boolean }>`
min-width: 400px;
height: 100%;
box-shadow: -1px 0px 10px darkgray;
background-color: #ffffff;
.mainContent {
height: 100%;
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
}
}
.ErrorContainer {
width: 100%;
height: 80%;
Expand Down
207 changes: 198 additions & 9 deletions client/src/components/StockOrderSection/OrderResult.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,126 @@
import { styled } from "styled-components";
import useGetStockOrderRecord from "../../hooks/useGetStockOrderRecord";
import useGetCompanyList from "../../hooks/useGetCompanyList";
import useDeleteStockOrder from "../../hooks/useDeleteStockOrder";
import { orderWaitProps } from "../../models/stockProps";

const titleText: string = "주문내역";
const orderPendingTitle: string = "미체결";
// dummyLogo
import dummyImg from "../../asset/CentralSectionMenu-dummyImg.png";

const priceUnit: string = "원";
const volumeUnit: string = "주";
const cancelButtonText: string = "주문취소";

const titleText: string = "미체결 내역";
const orderPendingEmptyMessage: string = "미체결 내역이 없습니다";
const layoutComposition: string = "StockHolm";

const OrderResult = () => {
const { orderRecordData } = useGetStockOrderRecord();

const orderWaitList = orderRecordData.filter((order: orderWaitProps) => order.orderStates === "ORDER_WAIT");
orderWaitList.reverse(); // 최근 주문이 상단에 노출되도록 배열 순서 변경
const waitListNum = orderWaitList.length;

console.log(waitListNum);

return (
<Container>
<div className="Title">{titleText}</div>
<OrderPending>
<div className="orderPendingTitle">{orderPendingTitle}</div>
<div className="emptyIndicator">{orderPendingEmptyMessage}</div>
</OrderPending>
<TradeWaiting>
{waitListNum === 0 ? (
<div className="emptyIndicator">{orderPendingEmptyMessage}</div>
) : (
<div className="orderWaitStockList">
{orderWaitList.map((stock: orderWaitProps) => {
const orderType = stock.orderTypes === "BUY" ? "매수" : "매도";
const price = stock.price.toLocaleString();
const volume = stock.stockCount.toLocaleString();
const companyId = stock.companyId;
const orderId = stock.stockOrderId;

return <OrderWaitStock key={orderId} orderType={orderType} price={price} volume={volume} companyId={companyId} orderId={orderId} />;
})}
<div className="layoutComposition">{layoutComposition}</div>
</div>
)}
</TradeWaiting>
</Container>
);
};

export default OrderResult;

const OrderWaitStock = (props: WaitStockProps) => {
const { orderType, price, volume, companyId, orderId } = props;
const { companyList } = useGetCompanyList();
const deleteOrder = useDeleteStockOrder();

const corp = companyList.filter((corp: companyProps) => corp.companyId === companyId);
const corpName = corp[0].korName;

const handleDeleteWaitOrder = () => {
deleteOrder.mutate(orderId);
const { isLoading, isError } = deleteOrder;

if (isLoading) {
console.log("주문 삭제 처리 중");
}

if (isError) {
console.log("주문 삭제 실패");
}
};

return (
<StockContainer orderType={orderType}>
<div className="logoContainer">
<img className="corpLogo" src={dummyImg} />
</div>
<div className="tradingOverview">
<div className="corpName">{corpName}</div>
<div className="orderInfo">
<span className="orderType">{orderType}</span>
<span className="price">
{price}
{priceUnit}
</span>
<span className="volume">
{volume}
{volumeUnit}
</span>
</div>
</div>
<div className="buttonContainer">
<button className="cancelButton" onClick={handleDeleteWaitOrder}>
{cancelButtonText}
</button>
</div>
</StockContainer>
);
};

// type 정의
interface WaitStockProps {
orderType: string;
price: string;
volume: string;
companyId: number;
orderId: number;
}

interface companyProps {
code: string;
companyId: number;
korName: string;
stockAsBiResponseDto: null;
stockInfResponseDto: null;
}

// component 생성
const Container = styled.div`
flex: 1 0 0;
height: auto;
max-height: 100%;
padding-top: 16px;
display: flex;
flex-direction: column;
Expand All @@ -32,8 +133,8 @@ const Container = styled.div`
}
`;

const OrderPending = styled.div`
.orderPendingTitle {
const TradeWaiting = styled.div`
.title {
padding-left: 16px;
margin-bottom: 8px;
}
Expand All @@ -48,4 +149,92 @@ const OrderPending = styled.div`
font-weight: 350;
color: #999999;
}
.layoutComposition {
width: 100%;
height: 48px;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
font-weight: 350;
color: white;
}
`;

const StockContainer = styled.div<{ orderType: string }>`
width: 100%;
height: 48px;
padding-right: 16px;
padding-bottom: 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 2px;
.logoContainer {
flex: 1 0 0;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding-left: 4px;
.corpLogo {
width: 24px;
height: 24px;
border-radius: 50%;
}
}
.tradingOverview {
flex: 7 0 0;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
padding-left: 2px;
.corpName {
font-size: 14px;
font-weight: 500;
}
.orderInfo {
display: flex;
flex-direction: row;
gap: 4px;
font-size: 12px;
.orderType {
padding-left: 0.3px;
padding-right: 2px;
color: ${(props) => (props.orderType === "매도" ? "#2679ed" : "#e22926")};
}
.price,
.volume {
color: darkgray;
}
}
}
.buttonContainer {
flex: 1.4 0 0;
height: 100%;
display: flex;
align-items: center;
.cancelButton {
font-size: 11px;
padding-left: 6px;
padding-right: 6px;
height: 24px;
border: none;
border-radius: 0.2rem;
color: ${(props) => (props.orderType === "매도" ? "#4479c2" : "#cc3c3a")};
background-color: ${(props) => (props.orderType === "매도" ? "#c7dbfa" : "#f4d2cf")};
}
}
`;
32 changes: 32 additions & 0 deletions client/src/hooks/useDeleteStockOrder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useMutation, useQueryClient } from "react-query";
import axios from "axios";

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

const useDeleteStockOrder = () => {
const queryClient = useQueryClient();

const mutate = useMutation((orderId: number) => deleteStockOrder(orderId), {
onSuccess: () => {
queryClient.invalidateQueries("holdingStock");
queryClient.invalidateQueries("orderRecord");
},
});

return mutate;
};

export default useDeleteStockOrder;

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

const response = await axios.delete(`${url}${orderId}`, options);
return response;
};
2 changes: 1 addition & 1 deletion client/src/hooks/useGetCompanyList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default useGetCompanyList;
// 서버에서 Company 목록 fetch 하는 함수
const getCompanyList = async () => {
const res = await axios.get("http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/companies");
const companyList = res.data;
const companyList = await res.data;

return companyList;
};
12 changes: 7 additions & 5 deletions client/src/hooks/useGetStockData.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useState, useEffect } from "react";
import { useQuery } from "react-query";
import { useQuery, useQueryClient } from "react-query";
import axios from "axios";

const useGetStockData = (companyId: number) => {
const [autoRefetch, setAutoRefetch] = useState(false);
const queryClient = useQueryClient();

// 시간대 (timeZone) 별로 queryKey를 다르게 설정해서, 서버 데이터가 동일할 때는 캐싱된 데이터 활용하고 서버 데이터가 갱신됐을 때는 새롭게 받아옴 (서버 데이터 30분마다 갱신)
const currentTime = new Date();
Expand Down Expand Up @@ -33,10 +34,11 @@ const useGetStockData = (companyId: number) => {
const { data, isLoading, error, refetch } = useQuery(`chartData${companyId} ${queryKey}`, () => getChartData(companyId), {
enabled: true,
refetchInterval: autoRefetch ? 60000 * 10 : false, // 정각 혹은 30분에 맞춰서 10분 마다 데이터 리패칭
// onSuccess: () => {
// console.log(new Date());
// console.log(data);
// },
onSuccess: () => {
queryClient.invalidateQueries("cash");
queryClient.invalidateQueries("holdingStock");
queryClient.invalidateQueries("orderRecord");
},
});

return { stockPrice: data, stockPriceLoading: isLoading, stockPriceError: error };
Expand Down
2 changes: 0 additions & 2 deletions client/src/hooks/useTradeStock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ const postOrderRequest = async (orderType: boolean, companyId: number, price: nu
const response = await axios.post(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/stock/buy?companyId=${companyId}&price=${price}&stockCount=${volume}`, {}, options);
const orderResult = await response.data;

console.log(orderResult); // 테스트 코드
return orderResult;
}

Expand All @@ -46,7 +45,6 @@ const postOrderRequest = async (orderType: boolean, companyId: number, price: nu
const response = await axios.post(`http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/stock/sell?companyId=${companyId}&price=${price}&stockCount=${volume}`, {}, options);
const orderResult = await response.data;

console.log(orderResult); // 테스트 코드
return orderResult;
}
};
Loading

0 comments on commit 3838840

Please sign in to comment.