Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] 주식 상세페이지 웹소켓 연결 & 거래창 UI 개선 #204

Merged
merged 13 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions FE/src/components/Mypage/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import MyStocksList from './MyStocksList';
import { getAssets } from 'service/assets';

export default function Account() {
const { data, isLoading, isError } = useQuery(['account', 'assets'], () =>
getAssets(),
const { data, isLoading, isError } = useQuery(
['account', 'assets'],
() => getAssets(),
{ staleTime: 1000 },
);

if (isLoading) return <div>loading</div>;
Expand All @@ -15,7 +17,7 @@ export default function Account() {
const { asset, stocks } = data;

return (
<div className='flex flex-col gap-3'>
<div className='flex min-h-[500px] flex-col gap-3'>
<AccountCondition asset={asset} />
<MyStocksList stocks={stocks} />
</div>
Expand Down
38 changes: 38 additions & 0 deletions FE/src/components/Mypage/CancleAlertModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Overay from 'components/ModalOveray';
import useOrderCancelAlertModalStore from 'store/orderCancleAlertModalStore';

export default function CancleAlertModal() {
const { close, onSuccess, order } = useOrderCancelAlertModalStore();
if (!order) return;

const { stock_name, amount, trade_type } = order;

return (
<>
<Overay onClick={() => close()} />
<section className='fixed left-1/2 top-1/2 flex w-[440px] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 rounded-2xl bg-white p-7 shadow-lg'>
<div className='text-lg font-semibold'>
{stock_name} {amount}주 {trade_type === 'BUY' ? '매수' : '매도'} 주문
요청을 취소하시겠습니까?
</div>
<div className='flex justify-center gap-2'>
<button
className='w-24 px-6 py-2 text-white transition rounded-xl bg-juga-grayscale-500 hover:bg-juga-grayscale-black'
onClick={() => {
onSuccess();
close();
}}
>
</button>
<button
className='w-24 px-6 py-2 text-gray-800 rounded-xl bg-juga-grayscale-100 hover:bg-juga-grayscale-200'
onClick={close}
>
아니오
</button>
</div>
</section>
</>
);
}
35 changes: 35 additions & 0 deletions FE/src/components/Mypage/MyInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useQuery } from '@tanstack/react-query';
import { getMyProfile } from 'service/user';

export default function MyInfo() {
const { data, isLoading, isError } = useQuery(
['myInfo', 'profile'],
() => getMyProfile(),
{ staleTime: 1000 },
);

if (isLoading) return <div>loading</div>;
if (!data) return <div>No data</div>;
if (isError) return <div>error</div>;

const { name, email } = data;

return (
<div className='flex flex-col items-center p-6 text-lg'>
<div className='w-full px-40'>
<div className='flex items-center justify-between py-2 border-b'>
<p className='font-medium text-left text-jugagrayscale-400 w-28'>
Username
</p>
<p className='font-semibold text-jugagrayscale-500'>{name}</p>
</div>
<div className='flex items-center justify-between py-2'>
<p className='font-medium text-left text-jugagrayscale-400 w-28'>
Email
</p>
<p className='font-semibold text-jugagrayscale-500'>{email}</p>
</div>
</div>
</div>
);
}
4 changes: 2 additions & 2 deletions FE/src/components/Mypage/MyStocksList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ type MyStocksListProps = {

export default function MyStocksList({ stocks }: MyStocksListProps) {
return (
<div className='flex flex-col w-full p-4 mx-auto bg-white rounded-md shadow-md'>
<div className='flex flex-col flex-1 w-full p-4 mx-auto bg-white rounded-md shadow-md'>
<div className='flex pb-2 text-sm font-bold border-b'>
<p className='w-1/2 text-left truncate'>종목</p>
<p className='w-1/4 text-center'>보유 수량</p>
<p className='w-1/4 text-right'>평균 가격</p>
</div>

<ul className='flex flex-col text-sm divide-y min-h-48'>
<ul className='flex flex-col text-sm divide-y'>
{stocks.map((stock) => {
const { code, name, avg_price, quantity } = stock;
return (
Expand Down
16 changes: 8 additions & 8 deletions FE/src/components/Mypage/Order.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import useOrders from 'hooks/useOrder';
import useOrderCancelAlertModalStore from 'store/orderCancleAlertModalStore';
import { parseTimestamp } from 'utils/common';
import CancleAlertModal from './CancleAlertModal';

export default function Order() {
const { orderQuery, removeOrder } = useOrders();

const { data, isLoading, isError } = orderQuery;
const { isOpen, open } = useOrderCancelAlertModalStore();

if (isLoading) return <div>loading</div>;
if (!data) return <div>No data</div>;
if (isError) return <div>error</div>;

const handleCancelOrder = (id: number) => {
removeOrder.mutate(id);
};

return (
<div className='flex flex-col w-full p-4 mx-auto bg-white rounded-md shadow-md'>
<div className='mx-auto flex min-h-[500px] w-full flex-col rounded-md bg-white p-4 shadow-md'>
<div className='flex pb-2 text-sm font-bold border-b'>
<p className='w-1/3 text-left truncate'>종목</p>
<p className='w-1/4 text-center'>요청 유형</p>
Expand All @@ -25,7 +24,7 @@ export default function Order() {
<p className='w-1/6 text-right'></p>
</div>

<ul className='flex flex-col text-sm divide-y min-h-48'>
<ul className='flex flex-col text-sm divide-y'>
{data.map((order) => {
const {
id,
Expand Down Expand Up @@ -59,8 +58,8 @@ export default function Order() {
</p>
<p className='w-1/6 text-right'>
<button
onClick={() => handleCancelOrder(id)}
className='px-2 py-1 text-xs text-white transition rounded-lg bg-juga-grayscale-500 hover:bg-red-600'
onClick={() => open(order, () => removeOrder.mutate(id))}
className='px-2 py-1 text-xs text-white transition rounded-lg bg-juga-grayscale-500 hover:bg-juga-grayscale-black'
>
취소
</button>
Expand All @@ -69,6 +68,7 @@ export default function Order() {
);
})}
</ul>
{isOpen && <CancleAlertModal />}
</div>
);
}
15 changes: 8 additions & 7 deletions FE/src/components/StocksDetail/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,13 @@
y: 0,
});

const { data, isLoading } = useQuery({
queryKey: ['stocksChartData', code, timeCategory],
queryFn: () => getStocksChartDataByCode(code, timeCategory),
staleTime: 1000,
cacheTime: 1000 * 60,
});

const { data, isLoading } = useQuery(
['stocksChartData', code, timeCategory],
() => getStocksChartDataByCode(code, timeCategory),
{ staleTime: 1000 },
);


const handleMouseDown = useCallback((e: MouseEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -284,7 +285,7 @@
);
}
},
[

Check warning on line 288 in FE/src/components/StocksDetail/Chart.tsx

View workflow job for this annotation

GitHub Actions / FE-test-and-build

React Hook useCallback has unnecessary dependencies: 'drawBarChart', 'drawCandleChart', 'drawChartGrid', 'drawLineChart', 'drawLowerYAxis', 'drawUpperYAxis', 'drawXAxis', and 'padding'. Either exclude them or remove the dependency array. Outer scope values like 'padding' aren't valid dependencies because mutating them doesn't re-render the component
padding,
upperLabelNum,
lowerLabelNum,
Expand Down Expand Up @@ -360,7 +361,7 @@

return (
<div className='box-border flex h-[260px] flex-col items-center rounded-lg bg-white p-3'>
<div className='flex h-fit w-full items-center justify-between'>
<div className='flex items-center justify-between w-full h-fit'>
<p className='font-semibold'>차트</p>
<nav className='flex gap-4 text-sm'>
{categories.map(({ label, value }) => (
Expand Down
42 changes: 36 additions & 6 deletions FE/src/components/StocksDetail/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { useEffect, useState } from 'react';
import { unsbscribe } from 'service/stocks';
import { StockDetailType } from 'types';
import { stringToLocaleString } from 'utils/common';
import { socket } from 'utils/socket';

type StocksDeatailHeaderProps = {
code: string;
Expand All @@ -16,22 +20,48 @@ export default function Header({ code, data }: StocksDeatailHeaderProps) {
per,
} = data;

const [currPrice, setCurrPrice] = useState(stck_prpr);
const [currPrdyVrssSign, setCurrPrdyVrssSign] = useState(prdy_vrss_sign);
const [currPrdyVrss, setCurrPrdyVrss] = useState(prdy_vrss);
const [currPrdyRate, setCurrPrdyRate] = useState(prdy_ctrt);

useEffect(() => {
const handleSocketData = (data: {
stck_prpr: string;
prdy_vrss: string;
prdy_vrss_sign: string;
prdy_ctrt: string;
}) => {
const { stck_prpr, prdy_vrss, prdy_vrss_sign, prdy_ctrt } = data;
setCurrPrice(stck_prpr);
setCurrPrdyVrss(prdy_vrss);
setCurrPrdyVrssSign(prdy_vrss_sign);
setCurrPrdyRate(prdy_ctrt);
};

socket.on(`detail/${code}`, handleSocketData);

return () => {
unsbscribe(code);
};
}, [code]);

Comment on lines +28 to +48
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢이렇게 해도 소켓으로 정보가 잘 오나요? 이렇게 했을 때 저는 리렌더가 발생할 때 클린업 코드가 실행되서 서버에 구독해지가 가버려서 이후에 소켓으로 이벤트가 안오는데 ㅜ🥲

const stockInfo: { label: string; value: string }[] = [
{ label: '시총', value: `${Number(hts_avls).toLocaleString()}억원` },
{ label: 'PER', value: `${per}배` },
];

const colorStyleBySign =
prdy_vrss_sign === '3'
currPrdyVrssSign === '3'
? ''
: prdy_vrss_sign < '3'
: currPrdyVrssSign < '3'
? 'text-juga-red-60'
: 'text-juga-blue-40';

const percentAbsolute = Math.abs(Number(prdy_ctrt)).toFixed(2);
const percentAbsolute = Math.abs(Number(currPrdyRate)).toFixed(2);

const plusOrMinus =
prdy_vrss_sign === '3' ? '' : prdy_vrss_sign < '3' ? '+' : '-';
currPrdyVrssSign === '3' ? '' : currPrdyVrssSign < '3' ? '+' : '-';

return (
<div className='flex items-center justify-between w-full h-16 px-2'>
Expand All @@ -41,11 +71,11 @@ export default function Header({ code, data }: StocksDeatailHeaderProps) {
<p className='text-juga-grayscale-200'>{code}</p>
</div>
<div className='flex items-center gap-2'>
<p className='text-lg'>{Number(stck_prpr).toLocaleString()}원</p>
<p className='text-lg'>{stringToLocaleString(currPrice)}원</p>
<p>어제보다</p>
<p className={`${colorStyleBySign}`}>
{plusOrMinus}
{Math.abs(Number(prdy_vrss)).toLocaleString()}원 ({plusOrMinus}
{Math.abs(Number(currPrdyVrss)).toLocaleString()}원 ({plusOrMinus}
{percentAbsolute}%)
</p>
</div>
Expand Down
27 changes: 18 additions & 9 deletions FE/src/components/StocksDetail/TradeSection/BuySection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ type BuySectionProps = {
export default function BuySection({ code, detailInfo }: BuySectionProps) {
const { stck_prpr, stck_mxpr, stck_llam, hts_kor_isnm } = detailInfo;

const { data, isLoading, isError } = useQuery(['detail', 'cash'], () =>
getCash(),
const { data, isLoading, isError } = useQuery(
['detail', 'cash'],
() => getCash(),
{ staleTime: 1000 },
);

const [currPrice, setCurrPrice] = useState<string>(stck_prpr);
Expand All @@ -32,17 +34,24 @@ export default function BuySection({ code, detailInfo }: BuySectionProps) {
const timerRef = useRef<number | null>(null);

const handlePriceChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!isNumericString(e.target.value)) return;
const s = e.target.value.replace(/,/g, '');
if (!isNumericString(s)) return;
setCurrPrice(s);
};

setCurrPrice(e.target.value);
const handleCountChange = (e: ChangeEvent<HTMLInputElement>) => {
const s = e.target.value;
if (!isNumericString(s)) return;
setCount(+s);
};

if (isLoading) return <div>loading</div>;
if (!data) return <div>No data</div>;
if (isError) return <div>error</div>;

const handlePriceInputBlur = (e: FocusEvent<HTMLInputElement>) => {
const n = +e.target.value;
const n = +e.target.value.replace(/,/g, '');

if (n > +stck_mxpr) {
if (timerRef.current) {
clearTimeout(timerRef.current);
Expand Down Expand Up @@ -93,14 +102,14 @@ export default function BuySection({ code, detailInfo }: BuySectionProps) {
<p className='mr-3 w-14'>매수 가격</p>
<input
type='text'
value={currPrice}
value={(+currPrice).toLocaleString()}
onChange={handlePriceChange}
onBlur={handlePriceInputBlur}
className='flex-1 py-1 rounded-lg'
/>
</div>
{lowerLimitFlag && (
<div className='text-sm text-juga-red-60'>
<div className='text-xs text-juga-red-60'>
이 주식의 최소 가격은 {(+stck_llam).toLocaleString()}입니다.
</div>
)}
Expand All @@ -112,9 +121,9 @@ export default function BuySection({ code, detailInfo }: BuySectionProps) {
<div className='flex items-center justify-between h-12'>
<p className='mr-3 w-14'> 수량</p>
<input
type='number'
type='text'
value={count}
onChange={(e) => setCount(+e.target.value)}
onChange={handleCountChange}
className='flex-1 py-1 rounded-lg'
min={1}
/>
Expand Down
Loading
Loading