From e78f4fcfb9802f9b966a787960143de33d21f1ca Mon Sep 17 00:00:00 2001 From: Seo San Date: Mon, 18 Nov 2024 10:49:49 +0900 Subject: [PATCH 01/77] =?UTF-8?q?=F0=9F=92=84=20design:=20chart=20Y?= =?UTF-8?q?=EC=B6=95=20=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 6 +++--- FE/src/utils/chart.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 0c6d8f16..3469bf6f 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -53,7 +53,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { const padding = { top: 20, - right: 80, + right: 160, bottom: 10, left: 20, }; @@ -71,10 +71,10 @@ export default function Chart({ code }: StocksDeatailChartProps) { return (
-
+

차트

-
-
- - +
+ {/* Upper 차트 영역 */} +
+ +
- {/*
*/} - {/* */} - {/*
*/} -
- - + {/* Lower 차트 영역 */} +
+ +
-
- + {/* X축 영역 */} +
+
diff --git a/FE/src/utils/chart.ts b/FE/src/utils/chart.ts index d0d1a420..10552456 100644 --- a/FE/src/utils/chart.ts +++ b/FE/src/utils/chart.ts @@ -42,19 +42,14 @@ export function drawBarChart( width: number, height: number, padding: Padding, - weight: number = 0, // 0~1 y축 범위 가중치 ) { if (data.length === 0) return; const n = data.length; ctx.beginPath(); - const yMax = Math.round( - Math.max(...data.map((d) => +d.acml_vol)) * 1 + weight, - ); - const yMin = Math.round( - Math.min(...data.map((d) => +d.acml_vol)) * 1 - weight, - ); + const yMax = Math.round(Math.max(...data.map((d) => +d.acml_vol)) * 1.2); + const yMin = Math.round(Math.min(...data.map((d) => +d.acml_vol)) * 0.8); const gap = Math.floor(width / n); @@ -94,25 +89,6 @@ export function drawCandleChart( const yMax = Math.round(Math.max(...arr) * (1 + weight)); const yMin = Math.round(Math.min(...arr) * (1 - weight)); - const labels = getYAxisLabels(yMin, yMax); - labels.forEach((label) => { - const yPos = - padding.top + height - ((label - yMin) / (yMax - yMin)) * height; - - // 라벨 텍스트 그리기 - ctx.font = '20px Arial'; - ctx.fillStyle = '#000'; - ctx.textAlign = 'start'; - ctx.fillText(label.toLocaleString(), padding.left + width + 80, yPos + 5); - - // Y축 눈금선 그리기 - ctx.strokeStyle = '#ddd'; - ctx.beginPath(); - ctx.moveTo(padding.left, yPos); - ctx.lineTo(padding.left + width + 56, yPos); - ctx.stroke(); - }); - data.forEach((e, i) => { ctx.beginPath(); @@ -150,20 +126,6 @@ export function drawCandleChart( }); } -function getYAxisLabels(min: number, max: number) { - let a = min.toString().length - 1; - let k = 1; - while (a--) k *= 10; - - const start = Math.ceil(min / k) * k; - const end = Math.floor(max / k) * k; - const labels = []; - for (let value = start; value <= end; value += k) { - labels.push(value); - } - return labels; -} - export const drawChart = ( ctx: CanvasRenderingContext2D, data: { time: string; value: string; diff: string }[], @@ -250,3 +212,49 @@ export const drawChart = ( ctx.stroke(); } }; + +export const drawYLabel = ( + ctx: CanvasRenderingContext2D, + data: StockChartUnit[], + width: number, + height: number, + padding: Padding, + weight: number = 0, +) => { + const values = data + .map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc]) + .flat(); + + const yMax = Math.round(Math.max(...values) * (1 + weight)); + const yMin = Math.round(Math.min(...values) * (1 - weight)); + + ctx.clearRect( + 0, + 0, + width + padding.left + padding.right, + height + padding.top + padding.bottom, + ); + + // 라벨 갯수 + const step = Math.ceil((yMax - yMin) / 3); + const labels = []; + for (let value = yMin; value <= yMax; value += step) { + labels.push(Math.round(value)); + } + if (!labels.includes(yMax)) { + labels.push(yMax); + } + + ctx.font = '24px sans-serif'; + ctx.fillStyle = '#000'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + + labels.forEach((label) => { + const yPos = + padding.top + height - ((label - yMin) / (yMax - yMin)) * height; + + const formattedValue = label.toLocaleString(); + ctx.fillText(formattedValue, width / 2, yPos); + }); +}; From 9d8ac83349d8963ba5b33766fccc99024b1de434 Mon Sep 17 00:00:00 2001 From: Seo San Date: Mon, 18 Nov 2024 21:45:24 +0900 Subject: [PATCH 12/77] =?UTF-8?q?=E2=9C=A8=20feat:=20X=EC=B6=95,=20Y?= =?UTF-8?q?=EC=B6=95=20=EC=84=A0=20=EC=83=9D=EC=84=B1=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 23 ++++++---- FE/src/utils/chart.ts | 57 ++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 43a232bd..00e56bfc 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -4,7 +4,8 @@ import { drawBarChart, drawCandleChart, drawLineChart, - drawYLabel, + drawLowerYLabel, + drawUpperYLabel, } from 'utils/chart'; import { useQuery } from '@tanstack/react-query'; import { getStocksChartDataByCode } from 'service/stocks'; @@ -61,25 +62,22 @@ export default function Chart({ code }: StocksDeatailChartProps) { const chartWidth = displayWidth * 0.9; const yAxisWidth = displayWidth * 0.1; - // Upper 차트 영역 설정 + // 차트 영역 설정 upperChartCanvas.width = chartWidth * 2; upperChartCanvas.height = upperHeight * 2; upperChartCanvas.style.width = `${chartWidth}px`; upperChartCanvas.style.height = `${upperHeight}px`; - // Upper Y축 영역 설정 upperChartYCanvas.width = yAxisWidth * 2; upperChartYCanvas.height = upperHeight * 2; upperChartYCanvas.style.width = `${yAxisWidth}px`; upperChartYCanvas.style.height = `${upperHeight}px`; - // Lower 차트 영역 설정 (Upper와 동일한 비율) lowerChartCanvas.width = chartWidth * 2; lowerChartCanvas.height = lowerHeight * 2; lowerChartCanvas.style.width = `${chartWidth}px`; lowerChartCanvas.style.height = `${lowerHeight}px`; - // Lower Y축 영역 설정 lowerChartYCanvas.width = yAxisWidth * 2; lowerChartYCanvas.height = lowerHeight * 2; lowerChartYCanvas.style.width = `${yAxisWidth}px`; @@ -109,7 +107,6 @@ export default function Chart({ code }: StocksDeatailChartProps) { const arr = data.map((e) => +e.stck_oprc); - // Upper 차트 그리기 drawLineChart( UpperChartCtx, arr, @@ -139,12 +136,11 @@ export default function Chart({ code }: StocksDeatailChartProps) { 0, 0, lowerChartWidth, - lowerChartHeight, + lowerChartHeight - padding.bottom, padding, ); - // Upper Y축 라벨 그리기 - drawYLabel( + drawUpperYLabel( UpperYCtx, data, upperChartYCanvas.width - padding.left - padding.right, @@ -152,6 +148,15 @@ export default function Chart({ code }: StocksDeatailChartProps) { padding, 0.1, ); + + drawLowerYLabel( + LowerYCtx, + data, + lowerChartYCanvas.width - padding.left - padding.right, + lowerChartYCanvas.height - padding.top - padding.bottom, + padding, + 0.1, + ); }, [timeCategory, data, isLoading]); return ( diff --git a/FE/src/utils/chart.ts b/FE/src/utils/chart.ts index 10552456..bc34324d 100644 --- a/FE/src/utils/chart.ts +++ b/FE/src/utils/chart.ts @@ -46,7 +46,13 @@ export function drawBarChart( if (data.length === 0) return; const n = data.length; - ctx.beginPath(); + // 캔버스 초기화 + ctx.clearRect( + 0, + 0, + width + padding.left + padding.right, + height + padding.top + padding.bottom, + ); const yMax = Math.round(Math.max(...data.map((d) => +d.acml_vol)) * 1.2); const yMin = Math.round(Math.min(...data.map((d) => +d.acml_vol)) * 0.8); @@ -56,7 +62,15 @@ export function drawBarChart( const blue = '#2175F3'; const red = '#FF3700'; + ctx.beginPath(); + ctx.moveTo(padding.left, height + 4); + ctx.lineTo(width + padding.left + padding.right, height + 4); + ctx.strokeStyle = '#D2DAE0'; + ctx.lineWidth = 2; + ctx.stroke(); + data.forEach((e, i) => { + ctx.beginPath(); const cx = x + padding.left + (width * i) / (n - 1); const cy = padding.top + ((height - y) * (+e.acml_vol - yMin)) / (yMax - yMin); @@ -64,8 +78,6 @@ export function drawBarChart( ctx.fillStyle = +e.stck_oprc < +e.stck_clpr ? red : blue; ctx.fillRect(cx, height, gap, -cy); }); - - ctx.stroke(); } export function drawCandleChart( @@ -213,7 +225,7 @@ export const drawChart = ( } }; -export const drawYLabel = ( +export const drawUpperYLabel = ( ctx: CanvasRenderingContext2D, data: StockChartUnit[], width: number, @@ -257,4 +269,41 @@ export const drawYLabel = ( const formattedValue = label.toLocaleString(); ctx.fillText(formattedValue, width / 2, yPos); }); + + ctx.beginPath(); + ctx.moveTo(padding.left, 0); + ctx.lineTo(padding.left, height + padding.top + padding.bottom); + ctx.strokeStyle = '#D2DAE0'; + ctx.lineWidth = 2; + ctx.stroke(); +}; + +export const drawLowerYLabel = ( + ctx: CanvasRenderingContext2D, + data: StockChartUnit[], + width: number, + height: number, + padding: Padding, + weight: number = 0, +) => { + ctx.clearRect( + 0, + 0, + width + padding.left + padding.right, + height + padding.top + padding.bottom, + ); + + // Y축 선 그리기 + ctx.beginPath(); + ctx.moveTo(padding.left, 0); + ctx.lineTo(padding.left, height + padding.top + 4); + ctx.strokeStyle = '#D2DAE0'; + ctx.lineWidth = 2; + ctx.stroke(); + + // X축 선 그리기 (바닥 선) + ctx.beginPath(); + ctx.moveTo(0, height + padding.top + 4); + ctx.lineTo(padding.left, height + padding.top + 4); + ctx.stroke(); }; From 8e69891a61592420c0d03b776dc8fe031ab48f0d Mon Sep 17 00:00:00 2001 From: Seo San Date: Mon, 18 Nov 2024 22:28:56 +0900 Subject: [PATCH 13/77] =?UTF-8?q?=F0=9F=94=A7=20fix:=20lint=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 2 -- FE/src/utils/chart.ts | 2 -- 2 files changed, 4 deletions(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 00e56bfc..26984c75 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -151,11 +151,9 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawLowerYLabel( LowerYCtx, - data, lowerChartYCanvas.width - padding.left - padding.right, lowerChartYCanvas.height - padding.top - padding.bottom, padding, - 0.1, ); }, [timeCategory, data, isLoading]); diff --git a/FE/src/utils/chart.ts b/FE/src/utils/chart.ts index bc34324d..2085c759 100644 --- a/FE/src/utils/chart.ts +++ b/FE/src/utils/chart.ts @@ -280,11 +280,9 @@ export const drawUpperYLabel = ( export const drawLowerYLabel = ( ctx: CanvasRenderingContext2D, - data: StockChartUnit[], width: number, height: number, padding: Padding, - weight: number = 0, ) => { ctx.clearRect( 0, From 94cd509cb68f0195ebf7eff7bddf5b71c44ac548 Mon Sep 17 00:00:00 2001 From: dongree Date: Mon, 18 Nov 2024 23:40:49 +0900 Subject: [PATCH 14/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=A7=A4=EC=88=98?= =?UTF-8?q?=EA=B0=80=EA=B2=A9,=20=EC=88=98=EB=9F=89=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=20&=20=EC=B4=9D=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=20=EB=82=98=ED=83=80=EB=82=98=EA=B2=8C=20?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=20#135?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/StocksDetail/TradeSection.tsx | 28 +++++++++++++++---- FE/src/page/StocksDetail.tsx | 2 +- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index bd987c94..48a057d7 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -3,11 +3,14 @@ import { useEffect, useRef, useState } from 'react'; import emptyAnimation from 'assets/emptyAnimation.json'; type TradeSectionProps = { - currPrice: string; + price: string; }; -export default function TradeSection({ currPrice }: TradeSectionProps) { +export default function TradeSection({ price }: TradeSectionProps) { const [category, setCategory] = useState<'buy' | 'sell'>('buy'); + const [currPrice, setCurrPrice] = useState(price); + const [count, setCount] = useState(0); + const indicatorRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); @@ -58,14 +61,23 @@ export default function TradeSection({ currPrice }: TradeSectionProps) {

매수 가격

{ + if (!isNumericString(e.target.value)) return; + setCurrPrice(e.target.value); + }} className='flex-1 py-1 rounded-lg' />

수량

- + setCount(+e.target.value)} + className='flex-1 py-1 rounded-lg' + />
@@ -78,7 +90,7 @@ export default function TradeSection({ currPrice }: TradeSectionProps) {

총 주문 금액

-

0원

+

{(+currPrice * count).toLocaleString()}원

@@ -94,3 +106,7 @@ export default function TradeSection({ currPrice }: TradeSectionProps) { ); } + +function isNumericString(str: string) { + return str.length === 0 || /^[0-9]+$/.test(str); +} diff --git a/FE/src/page/StocksDetail.tsx b/FE/src/page/StocksDetail.tsx index 01727f63..1005fb19 100644 --- a/FE/src/page/StocksDetail.tsx +++ b/FE/src/page/StocksDetail.tsx @@ -30,7 +30,7 @@ export default function StocksDetail() {
- +
); From 7c7b23eae9f3ff6d5a764342f1fde8663887df1e Mon Sep 17 00:00:00 2001 From: dongree Date: Mon, 18 Nov 2024 23:44:31 +0900 Subject: [PATCH 15/77] =?UTF-8?q?=F0=9F=92=84=20design:=20=EB=A7=A4?= =?UTF-8?q?=EB=8F=84=20=ED=83=AD=20=ED=99=94=EB=A9=B4=20UI=20=EB=B3=B4?= =?UTF-8?q?=EC=99=84=20#135?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/TradeSection.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index 48a057d7..defcb9ae 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -99,8 +99,13 @@ export default function TradeSection({ price }: TradeSectionProps) { ) : ( -
- +
+ +

매도할 주식이 없어요

)} From 486c327799c1c8eb6198b947f11534eaa142a8e0 Mon Sep 17 00:00:00 2001 From: dongree Date: Tue, 19 Nov 2024 00:27:01 +0900 Subject: [PATCH 16/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A3=BC=EC=8B=9D=20?= =?UTF-8?q?=EA=B0=80=EA=B2=A9=20=EC=83=81=ED=95=9C,=20=ED=95=98=ED=95=9C?= =?UTF-8?q?=20=EB=B2=94=EC=9C=84=20=EC=A0=81=EC=9A=A9=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84=20#135?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/StocksDetail/TradeSection.tsx | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index defcb9ae..94712679 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -1,5 +1,5 @@ import Lottie from 'lottie-react'; -import { useEffect, useRef, useState } from 'react'; +import { ChangeEvent, FocusEvent, useEffect, useRef, useState } from 'react'; import emptyAnimation from 'assets/emptyAnimation.json'; type TradeSectionProps = { @@ -7,12 +7,19 @@ type TradeSectionProps = { }; export default function TradeSection({ price }: TradeSectionProps) { + const upperLimit = Math.floor(+price * 1.3); + const lowerLimit = Math.floor(+price * 0.7); + const [category, setCategory] = useState<'buy' | 'sell'>('buy'); const [currPrice, setCurrPrice] = useState(price); + const [upperLimitFlag, setUpperLimitFlag] = useState(false); + const [lowerLimitFlag, setLowerLimitFlag] = useState(false); + const [count, setCount] = useState(0); const indicatorRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); + const timerRef = useRef(null); useEffect(() => { const idx = category === 'buy' ? 0 : 1; @@ -25,6 +32,41 @@ export default function TradeSection({ price }: TradeSectionProps) { } }, [category]); + const handlePriceChange = (e: ChangeEvent) => { + if (!isNumericString(e.target.value)) return; + + setCurrPrice(e.target.value); + }; + + const handlePriceInputBlur = (e: FocusEvent) => { + const n = +e.target.value; + if (n > upperLimit) { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + setCurrPrice(upperLimit.toString()); + + setUpperLimitFlag(true); + timerRef.current = setTimeout(() => { + setUpperLimitFlag(false); + }, 2000); + return; + } + + if (n < lowerLimit) { + if (timerRef.current) { + clearTimeout(timerRef.current); + } + setCurrPrice(lowerLimit.toString()); + + setLowerLimitFlag(true); + timerRef.current = setTimeout(() => { + setLowerLimitFlag(false); + }, 2000); + return; + } + }; + return (

주문하기

@@ -63,13 +105,21 @@ export default function TradeSection({ price }: TradeSectionProps) { { - if (!isNumericString(e.target.value)) return; - setCurrPrice(e.target.value); - }} + onChange={handlePriceChange} + onBlur={handlePriceInputBlur} className='flex-1 py-1 rounded-lg' />
+ {lowerLimitFlag && ( +
+ 이 주식의 최소 가격은 {lowerLimit.toLocaleString()}입니다. +
+ )} + {upperLimitFlag && ( +
+ 이 주식의 최대 가격은 {upperLimit.toLocaleString()}입니다. +
+ )}

수량

Date: Tue, 19 Nov 2024 13:09:32 +0900 Subject: [PATCH 17/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EA=B1=B0=EB=9E=98?= =?UTF-8?q?=ED=98=84=ED=99=A9=20SSE=20=EC=97=B0=EB=8F=99=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20#61?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/StocksDetail/PriceSection.tsx | 82 +++++++++---------- .../StocksDetail/PriceSectionSseHook.ts | 2 +- 2 files changed, 38 insertions(+), 46 deletions(-) diff --git a/FE/src/components/StocksDetail/PriceSection.tsx b/FE/src/components/StocksDetail/PriceSection.tsx index 8266d8b5..85d74c57 100644 --- a/FE/src/components/StocksDetail/PriceSection.tsx +++ b/FE/src/components/StocksDetail/PriceSection.tsx @@ -3,51 +3,49 @@ import PriceTableColumn from './PriceTableColumn.tsx'; import PriceTableLiveCard from './PriceTableLiveCard.tsx'; import PriceTableDayCard from './PriceTableDayCard.tsx'; import { useParams } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { DailyPriceDataType, PriceDataType } from './PriceDataType.ts'; import { getTradeHistory } from 'service/getTradeHistory.ts'; -// import { createSSEConnection } from './PriceSectionSseHook.ts'; +import { createSSEConnection } from './PriceSectionSseHook.ts'; export default function PriceSection() { + const { id } = useParams(); const [buttonFlag, setButtonFlag] = useState(true); const indicatorRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); - const { id } = useParams(); - // const [tradeData, setTradeData] = useState([]); - // const queryClient = useQueryClient(); - const { data: initialData = [], isLoading } = useQuery({ + const queryClient = useQueryClient(); + + const { data: tradeData = [], isLoading } = useQuery({ queryKey: ['detail', id, buttonFlag], queryFn: () => getTradeHistory(id as string, buttonFlag), - // refetchInterval: 1000, cacheTime: 30000, staleTime: 1000, }); - // const addData = (newData: PriceDataType) => { - // setTradeData((prev) => [...prev, newData]); - // - // queryClient.setQueryData( - // ['detail', id, buttonFlag], - // (old: PriceDataType[] = []) => { - // return [...old, newData]; - // }, - // ); - // }; + const addData = (newData: PriceDataType) => { + queryClient.setQueryData( + ['detail', id, buttonFlag], + (old: PriceDataType[] = []) => { + return [newData, ...old].slice(0, 30); + }, + ); + }; - // useEffect(() => { - // const targetWord = buttonFlag ? 'today' : 'daily'; - // - // const eventSource = createSSEConnection( - // `http://223.130.151.42:3000/api/stocks/trade-history/${id}/${targetWord}-sse`, - // addData, - // ); - // - // return () => { - // if (eventSource) { - // eventSource.close(); - // } - // }; - // }, [buttonFlag, id]); + useEffect(() => { + const targetWord = buttonFlag ? 'today' : 'daily'; + + const eventSource = createSSEConnection( + `http://223.130.151.42:3000/api/stocks/trade-history/${id}/${targetWord}-sse`, + addData, + ); + + return () => { + if (eventSource) { + console.log('SSE connection close'); + eventSource.close(); + } + }; + }, [buttonFlag, id]); useEffect(() => { const tmpIndex = buttonFlag ? 0 : 1; @@ -60,10 +58,6 @@ export default function PriceSection() { } }, [buttonFlag]); - // useEffect(() => { - // setTradeData(initialData); - // }, [initialData]); - return (
Loading... - ) : !initialData ? ( + ) : !tradeData ? ( No data available ) : buttonFlag ? ( - initialData.map((eachData: PriceDataType, index: number) => ( + tradeData.map((eachData: PriceDataType, index: number) => ( )) ) : ( - initialData.map( - (eachData: DailyPriceDataType, index: number) => ( - - ), - ) + tradeData.map((eachData: DailyPriceDataType, index: number) => ( + + )) )} diff --git a/FE/src/components/StocksDetail/PriceSectionSseHook.ts b/FE/src/components/StocksDetail/PriceSectionSseHook.ts index e384b06c..237657e2 100644 --- a/FE/src/components/StocksDetail/PriceSectionSseHook.ts +++ b/FE/src/components/StocksDetail/PriceSectionSseHook.ts @@ -9,7 +9,7 @@ export const createSSEConnection = ( eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); - onMessage(data); + onMessage(data.tradeData); } catch (error) { console.error('Failed to parse SSE message:', error); } From ca8820349edea28f391f1a1c76ac188256c88184 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 13:10:37 +0900 Subject: [PATCH 18/77] =?UTF-8?q?=F0=9F=92=84design:=20=EC=B0=A8=ED=8A=B8?= =?UTF-8?q?=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 26984c75..44571401 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -185,7 +185,7 @@ export default function Chart({ code }: StocksDeatailChartProps) {
{/* X축 영역 */} -
+
From 5d6dc7810b2fcced8cc45ec4f847f36ec45ab0b5 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 13:19:46 +0900 Subject: [PATCH 19/77] =?UTF-8?q?=F0=9F=94=A7=20fix:=20SSE=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20#61=20SSE?= =?UTF-8?q?=EC=9D=98=20=EA=B2=BD=EC=9A=B0=20=EB=8B=B9=EC=9D=BC=20=EC=8B=A4?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A7=8C=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/PriceSection.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FE/src/components/StocksDetail/PriceSection.tsx b/FE/src/components/StocksDetail/PriceSection.tsx index 85d74c57..0030c070 100644 --- a/FE/src/components/StocksDetail/PriceSection.tsx +++ b/FE/src/components/StocksDetail/PriceSection.tsx @@ -32,10 +32,9 @@ export default function PriceSection() { }; useEffect(() => { - const targetWord = buttonFlag ? 'today' : 'daily'; - + if (!buttonFlag) return; const eventSource = createSSEConnection( - `http://223.130.151.42:3000/api/stocks/trade-history/${id}/${targetWord}-sse`, + `http://223.130.151.42:3000/api/stocks/trade-history/${id}/today-sse`, addData, ); From cb76a016f7a257480af6033f5ecc9b5b8e7eeff5 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 14:03:19 +0900 Subject: [PATCH 20/77] =?UTF-8?q?=E2=9C=A8=20feat:=20Y=EA=B2=A9=EC=9E=90?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/utils/chart.ts | 52 ++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/FE/src/utils/chart.ts b/FE/src/utils/chart.ts index 2085c759..3cc5addf 100644 --- a/FE/src/utils/chart.ts +++ b/FE/src/utils/chart.ts @@ -94,12 +94,25 @@ export function drawCandleChart( const n = data.length; - const arr = data.map((d) => - Math.max(+d.stck_clpr, +d.stck_oprc, +d.stck_hgpr, +d.stck_lwpr), - ); + const values = data + .map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc]) + .flat(); + const yMax = Math.round(Math.max(...values) * (1 + weight)); + const yMin = Math.round(Math.min(...values) * (1 - weight)); - const yMax = Math.round(Math.max(...arr) * (1 + weight)); - const yMin = Math.round(Math.min(...arr) * (1 - weight)); + const labels = makeYLabels(yMax, yMin); + + ctx.beginPath(); + labels.forEach((label) => { + const yPos = + padding.top + height - ((label - yMin) / (yMax - yMin)) * height; + + ctx.moveTo(0, yPos); + ctx.lineTo(width + padding.left + padding.right, yPos); + }); + ctx.strokeStyle = '#D2DAE0'; + ctx.lineWidth = 2; + ctx.stroke(); data.forEach((e, i) => { ctx.beginPath(); @@ -236,7 +249,6 @@ export const drawUpperYLabel = ( const values = data .map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc]) .flat(); - const yMax = Math.round(Math.max(...values) * (1 + weight)); const yMin = Math.round(Math.min(...values) * (1 - weight)); @@ -247,30 +259,24 @@ export const drawUpperYLabel = ( height + padding.top + padding.bottom, ); - // 라벨 갯수 - const step = Math.ceil((yMax - yMin) / 3); - const labels = []; - for (let value = yMin; value <= yMax; value += step) { - labels.push(Math.round(value)); - } - if (!labels.includes(yMax)) { - labels.push(yMax); - } + const labels = makeYLabels(yMax, yMin); ctx.font = '24px sans-serif'; ctx.fillStyle = '#000'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; + ctx.beginPath(); labels.forEach((label) => { const yPos = padding.top + height - ((label - yMin) / (yMax - yMin)) * height; const formattedValue = label.toLocaleString(); + ctx.moveTo(0, yPos); + ctx.lineTo(padding.left, yPos); ctx.fillText(formattedValue, width / 2, yPos); }); - ctx.beginPath(); ctx.moveTo(padding.left, 0); ctx.lineTo(padding.left, height + padding.top + padding.bottom); ctx.strokeStyle = '#D2DAE0'; @@ -305,3 +311,17 @@ export const drawLowerYLabel = ( ctx.lineTo(padding.left, height + padding.top + 4); ctx.stroke(); }; + +const makeYLabels = (yMax: number, yMin: number) => { + const labels = []; + const rawTickInterval = Math.ceil((yMax - yMin) / 3); + const magnitude = 10 ** (String(rawTickInterval).length - 1); + const tickInterval = Math.floor(rawTickInterval / magnitude) * magnitude; + const startValue = Math.ceil(yMin / tickInterval) * tickInterval; + + for (let value = startValue; value <= yMax; value += tickInterval) { + labels.push(Math.round(value)); + } + + return labels; +}; From baf00612496ab592a1dfd407c4a6d72153770b7d Mon Sep 17 00:00:00 2001 From: dongree Date: Tue, 19 Nov 2024 13:04:48 +0900 Subject: [PATCH 21/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=A7=A4=EC=88=98=20?= =?UTF-8?q?API=20=EC=97=B0=EB=8F=99=20#137?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/StocksDetail/TradeSection.tsx | 27 ++++++++++++++++--- FE/src/page/StocksDetail.tsx | 2 +- FE/src/service/stocks.ts | 20 ++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index 94712679..2df14dbe 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -1,14 +1,25 @@ import Lottie from 'lottie-react'; -import { ChangeEvent, FocusEvent, useEffect, useRef, useState } from 'react'; +import { + ChangeEvent, + FocusEvent, + FormEvent, + useEffect, + useRef, + useState, +} from 'react'; import emptyAnimation from 'assets/emptyAnimation.json'; +import { buyStock } from 'service/stocks'; +import useAuthStore from 'store/authStore'; type TradeSectionProps = { + code: string; price: string; }; -export default function TradeSection({ price }: TradeSectionProps) { +export default function TradeSection({ code, price }: TradeSectionProps) { const upperLimit = Math.floor(+price * 1.3); const lowerLimit = Math.floor(+price * 0.7); + const { accessToken } = useAuthStore(); const [category, setCategory] = useState<'buy' | 'sell'>('buy'); const [currPrice, setCurrPrice] = useState(price); @@ -67,6 +78,16 @@ export default function TradeSection({ price }: TradeSectionProps) { } }; + const handleBuy = async (e: FormEvent) => { + e.preventDefault(); + if (!accessToken) { + console.log('accessToken 없음!'); + return; + } + const res = await buyStock(code, +currPrice, count, accessToken); + console.log(res); + }; + return (

주문하기

@@ -98,7 +119,7 @@ export default function TradeSection({ price }: TradeSectionProps) {
{category === 'buy' ? ( -
+

매수 가격

diff --git a/FE/src/page/StocksDetail.tsx b/FE/src/page/StocksDetail.tsx index 1005fb19..05d75a98 100644 --- a/FE/src/page/StocksDetail.tsx +++ b/FE/src/page/StocksDetail.tsx @@ -30,7 +30,7 @@ export default function StocksDetail() {
- +
); diff --git a/FE/src/service/stocks.ts b/FE/src/service/stocks.ts index 9f5d4bb4..cbecd464 100644 --- a/FE/src/service/stocks.ts +++ b/FE/src/service/stocks.ts @@ -22,3 +22,23 @@ export async function getStocksChartDataByCode( }), }).then((res) => res.json()); } + +export async function buyStock( + code: string, + price: number, + amount: number, + token: string, +) { + return fetch(`${import.meta.env.VITE_API_URL}/stocks/trade/buy`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + stock_code: code, + price, + amount, + }), + }); +} From 421d49c54d5ae5d41c53ff45ad85987dbf2589f4 Mon Sep 17 00:00:00 2001 From: dongree Date: Tue, 19 Nov 2024 14:18:12 +0900 Subject: [PATCH 22/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=9E=94=EC=95=A1=20?= =?UTF-8?q?=EB=B6=80=EC=A1=B1=20=EC=8B=9C=20alert=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/StocksDetail/TradeSection.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index 2df14dbe..78781a38 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -16,6 +16,8 @@ type TradeSectionProps = { price: string; }; +const MyAsset = 10000000; + export default function TradeSection({ code, price }: TradeSectionProps) { const upperLimit = Math.floor(+price * 1.3); const lowerLimit = Math.floor(+price * 0.7); @@ -25,6 +27,7 @@ export default function TradeSection({ code, price }: TradeSectionProps) { const [currPrice, setCurrPrice] = useState(price); const [upperLimitFlag, setUpperLimitFlag] = useState(false); const [lowerLimitFlag, setLowerLimitFlag] = useState(false); + const [lackAssetFlag, setLackAssetFlag] = useState(false); const [count, setCount] = useState(0); @@ -84,6 +87,17 @@ export default function TradeSection({ code, price }: TradeSectionProps) { console.log('accessToken 없음!'); return; } + + const price = +currPrice * count; + + if (price > MyAsset) { + setLackAssetFlag(true); + timerRef.current = setTimeout(() => { + setLackAssetFlag(false); + }, 2000); + return; + } + const res = await buyStock(code, +currPrice, count, accessToken); console.log(res); }; @@ -165,7 +179,12 @@ export default function TradeSection({ code, price }: TradeSectionProps) { - From 99c038afcdb35deb0e203a9f729a808aa0153886 Mon Sep 17 00:00:00 2001 From: dongree Date: Tue, 19 Nov 2024 14:29:01 +0900 Subject: [PATCH 23/77] =?UTF-8?q?=F0=9F=94=A7=20fix:=20=EC=A3=BC=EC=8B=9D?= =?UTF-8?q?=20=EC=83=81=ED=95=9C=EA=B0=80,=20=ED=95=98=ED=95=9C=EA=B0=80?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=EC=97=90=EC=84=9C=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B3=A0=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=EC=97=90=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/StocksDetail/TradeSection.tsx | 23 ++++++++++--------- FE/src/page/StocksDetail.tsx | 4 +--- FE/src/types.ts | 2 ++ 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index 78781a38..714ddbb6 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -10,21 +10,22 @@ import { import emptyAnimation from 'assets/emptyAnimation.json'; import { buyStock } from 'service/stocks'; import useAuthStore from 'store/authStore'; +import { StockDetailType } from 'types'; type TradeSectionProps = { code: string; - price: string; + data: StockDetailType; }; const MyAsset = 10000000; -export default function TradeSection({ code, price }: TradeSectionProps) { - const upperLimit = Math.floor(+price * 1.3); - const lowerLimit = Math.floor(+price * 0.7); +export default function TradeSection({ code, data }: TradeSectionProps) { + const { stck_prpr, stck_mxpr, stck_llam } = data; + const { accessToken } = useAuthStore(); const [category, setCategory] = useState<'buy' | 'sell'>('buy'); - const [currPrice, setCurrPrice] = useState(price); + const [currPrice, setCurrPrice] = useState(stck_prpr); const [upperLimitFlag, setUpperLimitFlag] = useState(false); const [lowerLimitFlag, setLowerLimitFlag] = useState(false); const [lackAssetFlag, setLackAssetFlag] = useState(false); @@ -54,11 +55,11 @@ export default function TradeSection({ code, price }: TradeSectionProps) { const handlePriceInputBlur = (e: FocusEvent) => { const n = +e.target.value; - if (n > upperLimit) { + if (n > +stck_mxpr) { if (timerRef.current) { clearTimeout(timerRef.current); } - setCurrPrice(upperLimit.toString()); + setCurrPrice(stck_mxpr); setUpperLimitFlag(true); timerRef.current = setTimeout(() => { @@ -67,11 +68,11 @@ export default function TradeSection({ code, price }: TradeSectionProps) { return; } - if (n < lowerLimit) { + if (n < +stck_llam) { if (timerRef.current) { clearTimeout(timerRef.current); } - setCurrPrice(lowerLimit.toString()); + setCurrPrice(stck_llam); setLowerLimitFlag(true); timerRef.current = setTimeout(() => { @@ -147,12 +148,12 @@ export default function TradeSection({ code, price }: TradeSectionProps) { {lowerLimitFlag && (
- 이 주식의 최소 가격은 {lowerLimit.toLocaleString()}입니다. + 이 주식의 최소 가격은 {(+stck_llam).toLocaleString()}입니다.
)} {upperLimitFlag && (
- 이 주식의 최대 가격은 {upperLimit.toLocaleString()}입니다. + 이 주식의 최대 가격은 {(+stck_mxpr).toLocaleString()}입니다.
)}
diff --git a/FE/src/page/StocksDetail.tsx b/FE/src/page/StocksDetail.tsx index 05d75a98..e30f2e11 100644 --- a/FE/src/page/StocksDetail.tsx +++ b/FE/src/page/StocksDetail.tsx @@ -20,8 +20,6 @@ export default function StocksDetail() { if (isLoading) return
Loading...
; if (!data) return
Non data
; - const { stck_prpr } = data; - return (
@@ -30,7 +28,7 @@ export default function StocksDetail() {
- +
); diff --git a/FE/src/types.ts b/FE/src/types.ts index c6160fb4..a2b97e89 100644 --- a/FE/src/types.ts +++ b/FE/src/types.ts @@ -26,6 +26,8 @@ export type StockDetailType = { prdy_ctrt: string; hts_avls: string; per: string; + stck_mxpr: string; + stck_llam: string; }; export type StockChartUnit = { From 6ff894a12ee3be6b69bc22c621dfe5d85ed1fc13 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 15:32:44 +0900 Subject: [PATCH 24/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EA=B1=B0=EB=9E=98?= =?UTF-8?q?=EB=9F=89=20=EC=B0=A8=ED=8A=B8=20Y=EC=B6=95=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8=20&=20=EA=B2=A9=EC=9E=90=EC=84=A0=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 27 +++-- FE/src/utils/chart.ts | 119 +++++++++++++++++------ 2 files changed, 104 insertions(+), 42 deletions(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 44571401..63f772a9 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -58,9 +58,9 @@ export default function Chart({ code }: StocksDeatailChartProps) { const displayHeight = parent.clientHeight; const upperHeight = displayHeight * 0.5; - const lowerHeight = displayHeight * 0.3; - const chartWidth = displayWidth * 0.9; - const yAxisWidth = displayWidth * 0.1; + const lowerHeight = displayHeight * 0.25; + const chartWidth = displayWidth * 0.92; + const yAxisWidth = displayWidth * 0.08; // 차트 영역 설정 upperChartCanvas.width = chartWidth * 2; @@ -133,10 +133,8 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawBarChart( LowerChartCtx, data, - 0, - 0, lowerChartWidth, - lowerChartHeight - padding.bottom, + lowerChartHeight - padding.top - padding.bottom, padding, ); @@ -151,6 +149,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawLowerYLabel( LowerYCtx, + data, lowerChartYCanvas.width - padding.left - padding.right, lowerChartYCanvas.height - padding.top - padding.bottom, padding, @@ -158,8 +157,8 @@ export default function Chart({ code }: StocksDeatailChartProps) { }, [timeCategory, data, isLoading]); return ( -
-
+
+

차트

-
+
{/* Upper 차트 영역 */}
From 37b7e514c3a1a878d57f0c52dce6c02b6f62e989 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 21:25:21 +0900 Subject: [PATCH 33/77] =?UTF-8?q?=E2=9C=A8=20feat:=20resize=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=83=9D=EC=84=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index b9cf63eb..83e3cb3d 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -177,7 +177,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { }, [timeCategory, data, isLoading]); return ( -
+

차트

+
+
+
{/* Lower 차트 영역 */}
From 14acbace23890cc259697825c5f04050ec6c1e08 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 22:08:19 +0900 Subject: [PATCH 34/77] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20&=20z-index=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Search/index.tsx | 6 ++---- FE/src/components/StocksDetail/Chart.tsx | 14 +++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/FE/src/components/Search/index.tsx b/FE/src/components/Search/index.tsx index 0173c5b1..0fc866c2 100644 --- a/FE/src/components/Search/index.tsx +++ b/FE/src/components/Search/index.tsx @@ -45,12 +45,10 @@ export default function SearchModal() { <> toggleSearchModal()} />
diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 83e3cb3d..e5a8d690 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -194,21 +194,21 @@ export default function Chart({ code }: StocksDeatailChartProps) {
{/* Upper 차트 영역 */} -
- - +
+ +
-
+
{/* Lower 차트 영역 */}
- - + +
{/* X축 영역 */}
- +
From 4824d80f98ca9341a39ac047aaf47dc957cc5d1c Mon Sep 17 00:00:00 2001 From: Seo San Date: Wed, 20 Nov 2024 13:42:01 +0900 Subject: [PATCH 35/77] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20Chart.ts?= =?UTF-8?q?=20=EB=82=B4=EB=B6=80=20=EC=BD=94=EB=93=9C=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20#123=20useEffect=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=EC=97=90=20=EC=9E=88=EB=8A=94=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=ED=99=94,=20=EC=B0=A8=ED=8A=B8=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EC=A6=88=20=EA=B4=80=EB=A0=A8=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20useState=EB=A1=9C=20=EA=B4=80=EB=A6=AC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 166 ++++++++++++++--------- FE/src/types.ts | 8 ++ 2 files changed, 107 insertions(+), 67 deletions(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index e5a8d690..9bab2dc8 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -1,5 +1,10 @@ import { useEffect, useRef, useState } from 'react'; -import { Padding, TiemCategory } from 'types'; +import { + ChartSizeConfigType, + Padding, + StockChartUnit, + TiemCategory, +} from 'types'; import { useQuery } from '@tanstack/react-query'; import { getStocksChartDataByCode } from 'service/stocks'; import { drawLineChart } from '../../utils/chart/drawLineChart.ts'; @@ -24,14 +29,6 @@ const padding: Padding = { left: 20, }; -const CHART_SIZE_CONFIG = { - upperHeight: 0.5, - lowerHeight: 0.4, - chartWidth: 0.92, - yAxisWidth: 0.08, - xAxisHeight: 0.1, -}; - type StocksDeatailChartProps = { code: string; }; @@ -45,6 +42,13 @@ export default function Chart({ code }: StocksDeatailChartProps) { const chartX = useRef(null); const [timeCategory, setTimeCategory] = useState('D'); + const [charSizeConfig, setChartSizeConfig] = useState({ + upperHeight: 0.5, + lowerHeight: 0.4, + chartWidth: 0.92, + yAxisWidth: 0.08, + xAxisHeight: 0.1, + }); const { data, isLoading } = useQuery( ['stocksChartData', code, timeCategory], @@ -53,57 +57,27 @@ export default function Chart({ code }: StocksDeatailChartProps) { const dimension = useDimensionsHook(containerRef); - useEffect(() => { - if (isLoading || !data) return; - - const parent = containerRef.current; - const upperChartCanvas = upperChartCanvasRef.current; - const lowerChartCanvas = lowerChartCanvasRef.current; - const upperChartYCanvas = upperChartY.current; - const lowerChartYCanvas = lowerChartY.current; - const chartXCanvas = chartX.current; - - if ( - !parent || - !upperChartCanvas || - !lowerChartCanvas || - !upperChartYCanvas || - !lowerChartYCanvas || - !chartXCanvas - ) - return; - - upperChartCanvas.width = dimension.width * CHART_SIZE_CONFIG.chartWidth * 2; - upperChartCanvas.height = - dimension.height * CHART_SIZE_CONFIG.upperHeight * 2; - upperChartCanvas.style.width = `${dimension.width * CHART_SIZE_CONFIG.chartWidth}px`; - upperChartCanvas.style.height = `${dimension.height * CHART_SIZE_CONFIG.upperHeight}px`; - - upperChartYCanvas.width = - dimension.width * CHART_SIZE_CONFIG.yAxisWidth * 2; - upperChartYCanvas.height = - dimension.height * CHART_SIZE_CONFIG.upperHeight * 2; - upperChartYCanvas.style.width = `${dimension.width * CHART_SIZE_CONFIG.yAxisWidth}px`; - upperChartYCanvas.style.height = `${dimension.height * CHART_SIZE_CONFIG.upperHeight}px`; - - lowerChartCanvas.width = dimension.width * CHART_SIZE_CONFIG.chartWidth * 2; - lowerChartCanvas.height = - dimension.height * CHART_SIZE_CONFIG.lowerHeight * 2; - lowerChartCanvas.style.width = `${dimension.width * CHART_SIZE_CONFIG.chartWidth}px`; - lowerChartCanvas.style.height = `${dimension.height * CHART_SIZE_CONFIG.lowerHeight}px`; - - lowerChartYCanvas.width = - dimension.width * CHART_SIZE_CONFIG.yAxisWidth * 2; - lowerChartYCanvas.height = - dimension.height * CHART_SIZE_CONFIG.lowerHeight * 2; - lowerChartYCanvas.style.width = `${dimension.width * CHART_SIZE_CONFIG.yAxisWidth}px`; - lowerChartYCanvas.style.height = `${dimension.height * CHART_SIZE_CONFIG.lowerHeight}px`; - - chartXCanvas.width = dimension.width * CHART_SIZE_CONFIG.chartWidth * 2; - chartXCanvas.height = dimension.height * CHART_SIZE_CONFIG.xAxisHeight * 2; - chartXCanvas.style.width = `${dimension.width * CHART_SIZE_CONFIG.chartWidth}px`; - chartXCanvas.style.height = `${dimension.height * CHART_SIZE_CONFIG.xAxisHeight}px`; - + const setCanvasSize = ( + canvas: HTMLCanvasElement, + widthConfig: number, + heightConfig: number, + ) => { + canvas.width = dimension.width * widthConfig * 2; + canvas.height = dimension.height * heightConfig * 2; + + // 스타일 크기 (실제 표시되는 크기) + canvas.style.width = `${dimension.width * widthConfig}px`; + canvas.style.height = `${dimension.height * heightConfig}px`; + }; + + const renderChart = ( + upperChartCanvas: HTMLCanvasElement, + lowerChartCanvas: HTMLCanvasElement, + upperChartYCanvas: HTMLCanvasElement, + lowerChartYCanvas: HTMLCanvasElement, + chartXCanvas: HTMLCanvasElement, + chartData: StockChartUnit[], + ) => { const UpperChartCtx = upperChartCanvas.getContext('2d'); const LowerChartCtx = lowerChartCanvas.getContext('2d'); const UpperYCtx = upperChartYCanvas.getContext('2d'); @@ -121,7 +95,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawLineChart( UpperChartCtx, - data, + chartData, 0, 0, upperChartCanvas.width - padding.left - padding.right, @@ -132,7 +106,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawCandleChart( UpperChartCtx, - data, + chartData, 0, 0, upperChartCanvas.width - padding.left - padding.right, @@ -144,7 +118,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { // Lower 차트 그리기 drawBarChart( LowerChartCtx, - data, + chartData, lowerChartCanvas.width - padding.left - padding.right, lowerChartCanvas.height - padding.top - padding.bottom, padding, @@ -152,7 +126,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawUpperYAxis( UpperYCtx, - data, + chartData, upperChartYCanvas.width - padding.left - padding.right, upperChartYCanvas.height - padding.top - padding.bottom, padding, @@ -161,7 +135,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawLowerYAxis( LowerYCtx, - data, + chartData, lowerChartYCanvas.width - padding.left - padding.right, lowerChartYCanvas.height - padding.top - padding.bottom, padding, @@ -169,12 +143,70 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawXAxis( ChartXCtx, - data, + chartData, chartXCanvas.width - padding.left - padding.right, chartXCanvas.height, padding, ); - }, [timeCategory, data, isLoading]); + }; + + useEffect(() => { + if (isLoading || !data) return; + + if ( + !upperChartCanvasRef.current || + !lowerChartCanvasRef.current || + !upperChartY.current || + !lowerChartY.current || + !chartX.current + ) + return; + setCanvasSize( + upperChartCanvasRef.current, + charSizeConfig.chartWidth, + charSizeConfig.upperHeight, + ); + + setCanvasSize( + upperChartY.current, + charSizeConfig.yAxisWidth, + charSizeConfig.upperHeight, + ); + + setCanvasSize( + lowerChartCanvasRef.current, + charSizeConfig.chartWidth, + charSizeConfig.lowerHeight, + ); + + setCanvasSize( + lowerChartY.current, + charSizeConfig.yAxisWidth, + charSizeConfig.lowerHeight, + ); + + setCanvasSize( + chartX.current, + charSizeConfig.chartWidth, + charSizeConfig.xAxisHeight, + ); + + renderChart( + upperChartCanvasRef.current, + lowerChartCanvasRef.current, + upperChartY.current, + lowerChartY.current, + chartX.current, + data, + ); + }, [ + timeCategory, + data, + isLoading, + setCanvasSize, + renderChart, + charSizeConfig, + ]); return (
diff --git a/FE/src/types.ts b/FE/src/types.ts index a2b97e89..4878b87e 100644 --- a/FE/src/types.ts +++ b/FE/src/types.ts @@ -39,3 +39,11 @@ export type StockChartUnit = { acml_vol: string; prdy_vrss_sign: string; }; + +export type ChartSizeConfigType = { + upperHeight: number; + lowerHeight: number; + chartWidth: number; + yAxisWidth: number; + xAxisHeight: number; +}; From d18b4d441d58699e15e1cac95d5025ea0baf2ece Mon Sep 17 00:00:00 2001 From: dongree Date: Wed, 20 Nov 2024 15:44:08 +0900 Subject: [PATCH 36/77] =?UTF-8?q?=F0=9F=94=A7=20fix:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=A9=EC=8B=9D=20=EC=BF=A0=ED=82=A4=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20&=20production=20dev=20mode=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/package-lock.json | 16 ++++++++++++ FE/package.json | 1 + FE/src/components/Login/index.tsx | 25 ++++++++++++++++--- .../StocksDetail/TradeAlertModal.tsx | 14 +++++------ .../components/StocksDetail/TradeSection.tsx | 12 +++------ FE/src/service/stocks.ts | 15 ++++++----- FE/vite.config.ts | 25 +++++++++++++------ 7 files changed, 73 insertions(+), 35 deletions(-) diff --git a/FE/package-lock.json b/FE/package-lock.json index ace223aa..6f98b127 100644 --- a/FE/package-lock.json +++ b/FE/package-lock.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@eslint/js": "^9.13.0", + "@types/node": "^22.9.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", @@ -1334,6 +1335,15 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/node": { + "version": "22.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", + "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.8" + } + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", @@ -3970,6 +3980,12 @@ } } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", diff --git a/FE/package.json b/FE/package.json index 17a3e821..0637501e 100644 --- a/FE/package.json +++ b/FE/package.json @@ -22,6 +22,7 @@ }, "devDependencies": { "@eslint/js": "^9.13.0", + "@types/node": "^22.9.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", diff --git a/FE/src/components/Login/index.tsx b/FE/src/components/Login/index.tsx index a0948232..3699386f 100644 --- a/FE/src/components/Login/index.tsx +++ b/FE/src/components/Login/index.tsx @@ -33,6 +33,22 @@ export default function Login() { toggleModal(); }; + const handleKakaoBtnClick = async () => { + if (import.meta.env.DEV) { + const res = await login('jindding', '1234'); + + if ('error' in res) { + setErrorCode(res.statusCode); + return; + } + + document.cookie = `accessToken=${res.accessToken}; path=/;`; + return; + } + + window.location.href = `${import.meta.env.VITE_API_URL}/auth/kakao`; + }; + return ( <> toggleModal()} /> @@ -67,11 +83,12 @@ export default function Login() { 로그인 - diff --git a/FE/src/components/StocksDetail/TradeAlertModal.tsx b/FE/src/components/StocksDetail/TradeAlertModal.tsx index 405873b1..28db30f1 100644 --- a/FE/src/components/StocksDetail/TradeAlertModal.tsx +++ b/FE/src/components/StocksDetail/TradeAlertModal.tsx @@ -1,6 +1,6 @@ import Overay from 'components/ModalOveray'; import { buyStock } from 'service/stocks'; -import useAuthStore from 'store/authStore'; +// import useAuthStore from 'store/authStore'; import useTradeAlertModalStore from 'store/tradeAlertModalStore'; type TradeAlertModalProps = { @@ -17,17 +17,17 @@ export default function TradeAlertModal({ count, }: TradeAlertModalProps) { const { toggleModal } = useTradeAlertModalStore(); - const { accessToken } = useAuthStore(); + // const { accessToken } = useAuthStore(); - if (!accessToken) { - console.log('accessToken 없음!'); - return; - } + // if (!accessToken) { + // console.log('accessToken 없음!'); + // return; + // } const charge = 55; // 수수료 임시 const handleBuy = async () => { - const res = await buyStock(code, +price, count, accessToken); + const res = await buyStock(code, +price, count); if (res.ok) toggleModal(); }; diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index cb6aebbd..cf8d322d 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -11,7 +11,6 @@ import emptyAnimation from 'assets/emptyAnimation.json'; import { StockDetailType } from 'types'; import useTradeAlertModalStore from 'store/tradeAlertModalStore'; import TradeAlertModal from './TradeAlertModal'; -import useAuthStore from 'store/authStore'; type TradeSectionProps = { code: string; @@ -29,7 +28,6 @@ export default function TradeSection({ code, data }: TradeSectionProps) { const [lowerLimitFlag, setLowerLimitFlag] = useState(false); const [lackAssetFlag, setLackAssetFlag] = useState(false); const { isOpen, toggleModal } = useTradeAlertModalStore(); - const { accessToken } = useAuthStore(); const [count, setCount] = useState(0); @@ -63,7 +61,7 @@ export default function TradeSection({ code, data }: TradeSectionProps) { setCurrPrice(stck_mxpr); setUpperLimitFlag(true); - timerRef.current = setTimeout(() => { + timerRef.current = window.setTimeout(() => { setUpperLimitFlag(false); }, 2000); return; @@ -76,7 +74,7 @@ export default function TradeSection({ code, data }: TradeSectionProps) { setCurrPrice(stck_llam); setLowerLimitFlag(true); - timerRef.current = setTimeout(() => { + timerRef.current = window.setTimeout(() => { setLowerLimitFlag(false); }, 2000); return; @@ -85,16 +83,12 @@ export default function TradeSection({ code, data }: TradeSectionProps) { const handleBuy = async (e: FormEvent) => { e.preventDefault(); - if (!accessToken) { - console.log('accessToken 없음!'); - return; - } const price = +currPrice * count; if (price > MyAsset) { setLackAssetFlag(true); - timerRef.current = setTimeout(() => { + timerRef.current = window.setTimeout(() => { setLackAssetFlag(false); }, 2000); return; diff --git a/FE/src/service/stocks.ts b/FE/src/service/stocks.ts index cbecd464..070a72e0 100644 --- a/FE/src/service/stocks.ts +++ b/FE/src/service/stocks.ts @@ -23,17 +23,16 @@ export async function getStocksChartDataByCode( }).then((res) => res.json()); } -export async function buyStock( - code: string, - price: number, - amount: number, - token: string, -) { - return fetch(`${import.meta.env.VITE_API_URL}/stocks/trade/buy`, { +export async function buyStock(code: string, price: number, amount: number) { + const url = import.meta.env.PROD + ? `${import.meta.env.VITE_API_URL}/stocks/trade/buy` + : '/api/stocks/trade/buy'; + + return fetch(url, { method: 'POST', + credentials: 'include', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, }, body: JSON.stringify({ stock_code: code, diff --git a/FE/vite.config.ts b/FE/vite.config.ts index be05188f..d2534332 100644 --- a/FE/vite.config.ts +++ b/FE/vite.config.ts @@ -1,12 +1,23 @@ -import { defineConfig } from 'vite'; +import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react'; import tsconfigPaths from 'vite-tsconfig-paths'; // https://vite.dev/config/ -export default defineConfig({ - plugins: [react(), tsconfigPaths()], - server: { - host: true, - open: true, - }, +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd()); + + return { + plugins: [react(), tsconfigPaths()], + server: { + host: true, + open: true, + proxy: { + '/api': { + target: env.VITE_SERVER_URL, + changeOrigin: true, + secure: false, + }, + }, + }, + }; }); From 0970be555fc6e5f8af19ea0812765823e58e6b06 Mon Sep 17 00:00:00 2001 From: Seo San Date: Wed, 20 Nov 2024 15:44:56 +0900 Subject: [PATCH 37/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=B0=A8=ED=8A=B8=20?= =?UTF-8?q?=EB=B9=84=EC=9C=A8=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20#123=20=EC=B0=A8=ED=8A=B8=20=EA=B0=80?= =?UTF-8?q?=EC=9A=B4=EB=8D=B0=EC=9D=98=20div=EB=A5=BC=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=EB=86=92=EC=9D=B4=EB=A5=BC=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 51 ++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 9bab2dc8..12bed288 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { MouseEvent, useEffect, useRef, useState } from 'react'; import { ChartSizeConfigType, Padding, @@ -49,14 +49,56 @@ export default function Chart({ code }: StocksDeatailChartProps) { yAxisWidth: 0.08, xAxisHeight: 0.1, }); + const [isDragging, setIsDragging] = useState(false); + const dimension = useDimensionsHook(containerRef); const { data, isLoading } = useQuery( ['stocksChartData', code, timeCategory], () => getStocksChartDataByCode(code, timeCategory), ); - const dimension = useDimensionsHook(containerRef); + const handleMouseDown = (e: MouseEvent) => { + e.preventDefault(); + setIsDragging(true); + }; + + const handleMouseMove = (e: globalThis.MouseEvent) => { + if (!isDragging || !containerRef.current) return; + const minHeight = 0.2; + const containerRect = containerRef.current.getBoundingClientRect(); + const mouseY = e.clientY - containerRect.top; + + let ratio = mouseY / dimension.height; + + const maxHeight = 0.9 - minHeight; + + const upperRatio = Math.min(maxHeight, Math.max(minHeight, ratio)); + const lowerRatio = 0.9 - upperRatio; + + if (lowerRatio >= minHeight && upperRatio >= minHeight) { + setChartSizeConfig((prev) => ({ + ...prev, + upperHeight: upperRatio, + lowerHeight: lowerRatio, + })); + } + }; + + const handleMouseUp = () => { + setIsDragging(false); + }; + + useEffect(() => { + if (isDragging) { + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('mouseup', handleMouseUp); + } + return () => { + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mouseup', handleMouseUp); + }; + }, [isDragging]); const setCanvasSize = ( canvas: HTMLCanvasElement, widthConfig: number, @@ -231,7 +273,10 @@ export default function Chart({ code }: StocksDeatailChartProps) {
-
+
{/* Lower 차트 영역 */}
From c4a85e59ddb58f5df8fbf3a992d14190c4a9dc9d Mon Sep 17 00:00:00 2001 From: dongree Date: Wed, 20 Nov 2024 15:59:46 +0900 Subject: [PATCH 38/77] =?UTF-8?q?=F0=9F=94=A7=20fix:=20test=20login,=20pw?= =?UTF-8?q?=20env=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Login/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FE/src/components/Login/index.tsx b/FE/src/components/Login/index.tsx index 3699386f..a0b03263 100644 --- a/FE/src/components/Login/index.tsx +++ b/FE/src/components/Login/index.tsx @@ -35,7 +35,10 @@ export default function Login() { const handleKakaoBtnClick = async () => { if (import.meta.env.DEV) { - const res = await login('jindding', '1234'); + const res = await login( + import.meta.env.VITE_TEST_ID, + import.meta.env.VITE_TEST_PW, + ); if ('error' in res) { setErrorCode(res.statusCode); From cc66b5399afbc8f63709284a94aa11723c5c0856 Mon Sep 17 00:00:00 2001 From: Seo San Date: Wed, 20 Nov 2024 16:15:38 +0900 Subject: [PATCH 39/77] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EB=8F=99=EB=A3=8C=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98?= =?UTF-8?q?=EC=98=81.=20=EC=A7=80=EC=A0=80=EB=B6=84=ED=95=B4=20=EB=B3=B4?= =?UTF-8?q?=EC=9D=B4=EB=8A=94=20=EC=83=81=EB=8C=80=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95.=20useEffect=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EB=B0=B0=EC=97=B4=20=EC=88=98=EC=A0=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Search/SearchCard.tsx | 4 ++-- FE/src/components/Search/SearchHistoryItem.tsx | 2 +- FE/src/components/StocksDetail/Chart.tsx | 15 +++++++-------- FE/src/components/StocksDetail/PriceSection.tsx | 2 +- .../components/StocksDetail/useDimensionsHook.ts | 2 +- FE/src/components/TopFive/Nav.tsx | 6 +++--- FE/src/components/TopFive/TopFive.tsx | 2 +- 7 files changed, 16 insertions(+), 17 deletions(-) diff --git a/FE/src/components/Search/SearchCard.tsx b/FE/src/components/Search/SearchCard.tsx index 9b47c1d2..860f09f4 100644 --- a/FE/src/components/Search/SearchCard.tsx +++ b/FE/src/components/Search/SearchCard.tsx @@ -1,7 +1,7 @@ import { SearchDataType } from './searchDataType.ts'; import { useNavigate } from 'react-router-dom'; -import useSearchModalStore from '../../store/useSearchModalStore.ts'; -import useSearchInputStore from '../../store/useSearchInputStore.ts'; +import useSearchModalStore from 'store/useSearchModalStore.ts'; +import useSearchInputStore from 'store/useSearchInputStore.ts'; import { SearchCardHighLight } from './SearchCardHighlight.tsx'; type SearchCardProps = { diff --git a/FE/src/components/Search/SearchHistoryItem.tsx b/FE/src/components/Search/SearchHistoryItem.tsx index 0c95e777..cd29943d 100644 --- a/FE/src/components/Search/SearchHistoryItem.tsx +++ b/FE/src/components/Search/SearchHistoryItem.tsx @@ -1,5 +1,5 @@ import { XMarkIcon } from '@heroicons/react/16/solid'; -import useSearchInputStore from '../../store/useSearchInputStore.ts'; +import useSearchInputStore from 'store/useSearchInputStore.ts'; type SearchHistoryItemProps = { item: string; diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 12bed288..8b3df22b 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -7,12 +7,12 @@ import { } from 'types'; import { useQuery } from '@tanstack/react-query'; import { getStocksChartDataByCode } from 'service/stocks'; -import { drawLineChart } from '../../utils/chart/drawLineChart.ts'; -import { drawCandleChart } from '../../utils/chart/drawCandleChart.ts'; -import { drawBarChart } from '../../utils/chart/drawBarChart.ts'; -import { drawXAxis } from '../../utils/chart/drawXAxis.ts'; -import { drawUpperYAxis } from '../../utils/chart/drawUpperYAxis.ts'; -import { drawLowerYAxis } from '../../utils/chart/drawLowerYAxis.ts'; +import { drawLineChart } from 'utils/chart/drawLineChart.ts'; +import { drawCandleChart } from 'utils/chart/drawCandleChart.ts'; +import { drawBarChart } from 'utils/chart/drawBarChart.ts'; +import { drawXAxis } from 'utils/chart/drawXAxis.ts'; +import { drawUpperYAxis } from 'utils/chart/drawUpperYAxis.ts'; +import { drawLowerYAxis } from 'utils/chart/drawLowerYAxis.ts'; import { useDimensionsHook } from './useDimensionsHook.ts'; const categories: { label: string; value: TiemCategory }[] = [ @@ -57,7 +57,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { () => getStocksChartDataByCode(code, timeCategory), ); - const handleMouseDown = (e: MouseEvent) => { + const handleMouseDown = (e: MouseEvent) => { e.preventDefault(); setIsDragging(true); }; @@ -107,7 +107,6 @@ export default function Chart({ code }: StocksDeatailChartProps) { canvas.width = dimension.width * widthConfig * 2; canvas.height = dimension.height * heightConfig * 2; - // 스타일 크기 (실제 표시되는 크기) canvas.style.width = `${dimension.width * widthConfig}px`; canvas.style.height = `${dimension.height * heightConfig}px`; }; diff --git a/FE/src/components/StocksDetail/PriceSection.tsx b/FE/src/components/StocksDetail/PriceSection.tsx index 0030c070..83cecaa5 100644 --- a/FE/src/components/StocksDetail/PriceSection.tsx +++ b/FE/src/components/StocksDetail/PriceSection.tsx @@ -34,7 +34,7 @@ export default function PriceSection() { useEffect(() => { if (!buttonFlag) return; const eventSource = createSSEConnection( - `http://223.130.151.42:3000/api/stocks/trade-history/${id}/today-sse`, + `${import.meta.env.VITE_API_URL}/stocks/trade-history/${id}/today-sse`, addData, ); diff --git a/FE/src/components/StocksDetail/useDimensionsHook.ts b/FE/src/components/StocksDetail/useDimensionsHook.ts index e0102ef2..14e18c7a 100644 --- a/FE/src/components/StocksDetail/useDimensionsHook.ts +++ b/FE/src/components/StocksDetail/useDimensionsHook.ts @@ -22,7 +22,7 @@ export const useDimensionsHook = (containerRef: RefObject) => { useEffect(() => { updateDimensions(); - }, [updateDimensions]); + }, [updateDimensions, containerRef]); return dimensions; }; diff --git a/FE/src/components/TopFive/Nav.tsx b/FE/src/components/TopFive/Nav.tsx index 5fc2bef3..e440602f 100644 --- a/FE/src/components/TopFive/Nav.tsx +++ b/FE/src/components/TopFive/Nav.tsx @@ -2,14 +2,14 @@ import { useSearchParams } from 'react-router-dom'; import { useEffect, useRef } from 'react'; import { MarketType } from './type.ts'; +const markets: MarketType[] = ['전체', '코스피', '코스닥', '코스피200']; + export default function Nav() { const [searchParams, setSearchParams] = useSearchParams(); const currentMarket = searchParams.get('top') || '전체'; const indicatorRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); - const markets: MarketType[] = ['전체', '코스피', '코스닥', '코스피200']; - const handleMarketChange = (market: MarketType) => { if (market === '전체') { searchParams.delete('top'); @@ -28,7 +28,7 @@ export default function Nav() { indicator.style.left = `${currentButton.offsetLeft}px`; indicator.style.width = `${currentButton.offsetWidth}px`; } - }, [currentMarket]); + }, [currentMarket, markets]); return (
diff --git a/FE/src/components/TopFive/TopFive.tsx b/FE/src/components/TopFive/TopFive.tsx index 903893ad..a56dec60 100644 --- a/FE/src/components/TopFive/TopFive.tsx +++ b/FE/src/components/TopFive/TopFive.tsx @@ -3,7 +3,7 @@ import Nav from './Nav'; import { useSearchParams } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { MarketType } from './type.ts'; -import { getTopFiveStocks } from '../../service/getTopFiveStocks.ts'; +import { getTopFiveStocks } from 'service/getTopFiveStocks.ts'; const paramsMap = { 전체: 'ALL', From d159730fe2d082d492405c1197fa8f77fdf81435 Mon Sep 17 00:00:00 2001 From: Seo San Date: Wed, 20 Nov 2024 17:09:13 +0900 Subject: [PATCH 40/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EA=B2=A9=EC=9E=90=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EB=8A=94=20=ED=95=A8=EC=88=98=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20#123=20=EA=B2=A9=EC=9E=90=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=8A=94=20=ED=95=A8=EC=88=98=EB=A5=BC=20=EB=94=B0=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=EC=8B=9C=EC=BC=9C=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20=EB=8C=80=EC=9D=91=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 17 +++++- FE/src/utils/chart/drawBarChart.ts | 26 --------- FE/src/utils/chart/drawCandleChart.ts | 17 ------ FE/src/utils/chart/drawChartGrid.ts | 71 ++++++++++++++++++++++++ FE/src/utils/chart/drawLowerYAxis.ts | 20 +------ FE/src/utils/chart/drawUpperYAxis.ts | 20 ++----- FE/src/utils/chart/makeLabels.ts | 2 +- 7 files changed, 95 insertions(+), 78 deletions(-) create mode 100644 FE/src/utils/chart/drawChartGrid.ts diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 8b3df22b..b1250200 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -14,6 +14,7 @@ import { drawXAxis } from 'utils/chart/drawXAxis.ts'; import { drawUpperYAxis } from 'utils/chart/drawUpperYAxis.ts'; import { drawLowerYAxis } from 'utils/chart/drawLowerYAxis.ts'; import { useDimensionsHook } from './useDimensionsHook.ts'; +import { drawChartGrid } from 'utils/chart/drawChartGrid.ts'; const categories: { label: string; value: TiemCategory }[] = [ { label: '일', value: 'D' }, @@ -134,6 +135,19 @@ export default function Chart({ code }: StocksDeatailChartProps) { ) return; + drawChartGrid( + UpperChartCtx, + upperChartCanvas.width - padding.left - padding.right, + upperChartCanvas.height - padding.top - padding.bottom, + 3, + LowerChartCtx, + lowerChartCanvas.width - padding.left - padding.right, + lowerChartCanvas.height - padding.top - padding.bottom, + 2, + chartData, + padding, + ); + drawLineChart( UpperChartCtx, chartData, @@ -156,7 +170,6 @@ export default function Chart({ code }: StocksDeatailChartProps) { 0.1, ); - // Lower 차트 그리기 drawBarChart( LowerChartCtx, chartData, @@ -170,6 +183,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { chartData, upperChartYCanvas.width - padding.left - padding.right, upperChartYCanvas.height - padding.top - padding.bottom, + 3, padding, 0.1, ); @@ -179,6 +193,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { chartData, lowerChartYCanvas.width - padding.left - padding.right, lowerChartYCanvas.height - padding.top - padding.bottom, + 2, padding, ); diff --git a/FE/src/utils/chart/drawBarChart.ts b/FE/src/utils/chart/drawBarChart.ts index 6fdda083..9d573c4f 100644 --- a/FE/src/utils/chart/drawBarChart.ts +++ b/FE/src/utils/chart/drawBarChart.ts @@ -1,5 +1,4 @@ import { Padding, StockChartUnit } from '../../types.ts'; -import { makeYLabels } from './makeLabels.ts'; export function drawBarChart( ctx: CanvasRenderingContext2D, @@ -10,36 +9,11 @@ export function drawBarChart( ) { if (data.length === 0) return; - ctx.clearRect( - 0, - 0, - width + padding.left + padding.right, - height + padding.top + padding.bottom, - ); - const volumes = data.map((d) => +d.acml_vol); const yMax = Math.round(Math.max(...volumes) * 1.2); const yMin = Math.round(Math.min(...volumes) * 0.8); const barWidth = Math.floor(width / data.length); - const labels = makeYLabels(yMax, yMin, 2); - - ctx.beginPath(); - - labels.forEach((label) => { - const valueRatio = (label - yMin) / (yMax - yMin); - const yPos = height - valueRatio * height; - - ctx.moveTo(0, yPos + padding.top); - ctx.lineTo(width + padding.left + padding.right, yPos + padding.top); - }); - - ctx.moveTo(0, height + padding.top); - ctx.lineTo(width + padding.left + padding.right, height + padding.top); - ctx.strokeStyle = '#D2DAE0'; - ctx.lineWidth = 2; - ctx.stroke(); - data.forEach((item, i) => { const value = +item.acml_vol; const valueRatio = (value - yMin) / (yMax - yMin); diff --git a/FE/src/utils/chart/drawCandleChart.ts b/FE/src/utils/chart/drawCandleChart.ts index 13402943..09095513 100644 --- a/FE/src/utils/chart/drawCandleChart.ts +++ b/FE/src/utils/chart/drawCandleChart.ts @@ -1,5 +1,4 @@ import { Padding, StockChartUnit } from '../../types.ts'; -import { makeYLabels } from './makeLabels.ts'; export function drawCandleChart( ctx: CanvasRenderingContext2D, @@ -11,8 +10,6 @@ export function drawCandleChart( padding: Padding, weight: number = 0, ) { - ctx.beginPath(); - const n = data.length; const values = data @@ -21,20 +18,6 @@ export function drawCandleChart( const yMax = Math.round(Math.max(...values) * (1 + weight)); const yMin = Math.round(Math.min(...values) * (1 - weight)); - const labels = makeYLabels(yMax, yMin, 3); - - ctx.beginPath(); - labels.forEach((label) => { - const yPos = - padding.top + height - ((label - yMin) / (yMax - yMin)) * height; - - ctx.moveTo(0, yPos); - ctx.lineTo(width + padding.left + padding.right, yPos); - }); - ctx.strokeStyle = '#D2DAE0'; - ctx.lineWidth = 2; - ctx.stroke(); - data.forEach((e, i) => { ctx.beginPath(); diff --git a/FE/src/utils/chart/drawChartGrid.ts b/FE/src/utils/chart/drawChartGrid.ts new file mode 100644 index 00000000..8206702e --- /dev/null +++ b/FE/src/utils/chart/drawChartGrid.ts @@ -0,0 +1,71 @@ +import { Padding, StockChartUnit } from '../../types.ts'; +import { makeYLabels } from './makeLabels.ts'; + +export const drawChartGrid = ( + upperChartCtx: CanvasRenderingContext2D, + upperChartWidth: number, + upperChartHeight: number, + upperLabelsNum: number, + lowerChartCtx: CanvasRenderingContext2D, + lowerChartWidth: number, + lowerChartHeight: number, + lowerLabelsNum: number, + data: StockChartUnit[], + padding: Padding, +) => { + const volumes = data.map((d) => +d.acml_vol); + const volumeMax = Math.round(Math.max(...volumes) * 1.2); + const volumeMin = Math.round(Math.min(...volumes) * 0.8); + const lowerLabels = makeYLabels(volumeMax, volumeMin, lowerLabelsNum); + + lowerChartCtx.beginPath(); + + lowerLabels.forEach((label) => { + const valueRatio = (label - volumeMin) / (volumeMax - volumeMin); + const yPos = lowerChartHeight - valueRatio * lowerChartHeight; + + lowerChartCtx.moveTo(0, yPos + padding.top); + lowerChartCtx.lineTo( + lowerChartWidth + padding.left + padding.right, + yPos + padding.top, + ); + }); + + lowerChartCtx.moveTo(0, lowerChartHeight + padding.top); + lowerChartCtx.lineTo( + lowerChartWidth + padding.left + padding.right, + lowerChartHeight + padding.top, + ); + lowerChartCtx.lineTo(lowerChartWidth + padding.left + padding.right, 0); + lowerChartCtx.strokeStyle = '#D2DAE0'; + lowerChartCtx.lineWidth = 2; + lowerChartCtx.stroke(); + + const values = data + .map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc]) + .flat(); + const yMax = Math.round(Math.max(...values) * (1 + 0.1)); + const yMin = Math.round(Math.min(...values) * (1 - 0.1)); + + const labels = makeYLabels(yMax, yMin, upperLabelsNum); + upperChartCtx.beginPath(); + labels.forEach((label) => { + const yPos = + padding.top + + upperChartHeight - + ((label - yMin) / (yMax - yMin)) * upperChartHeight; + upperChartCtx.moveTo(0, yPos); + upperChartCtx.lineTo(upperChartWidth + padding.left + padding.right, yPos); + }); + upperChartCtx.moveTo( + upperChartWidth + padding.left + padding.right, + upperChartHeight + padding.top + padding.bottom, + ); + upperChartCtx.lineTo( + upperChartWidth + padding.left + padding.right, + padding.top, + ); + upperChartCtx.strokeStyle = '#D2DAE0'; + upperChartCtx.lineWidth = 2; + upperChartCtx.stroke(); +}; diff --git a/FE/src/utils/chart/drawLowerYAxis.ts b/FE/src/utils/chart/drawLowerYAxis.ts index 48268d7d..f6c68ed3 100644 --- a/FE/src/utils/chart/drawLowerYAxis.ts +++ b/FE/src/utils/chart/drawLowerYAxis.ts @@ -6,6 +6,7 @@ export const drawLowerYAxis = ( data: StockChartUnit[], width: number, height: number, + labelsNum: number, padding: Padding, ) => { ctx.clearRect( @@ -15,22 +16,10 @@ export const drawLowerYAxis = ( height + padding.top + padding.bottom, ); - ctx.beginPath(); - ctx.moveTo(padding.left, 0); - ctx.lineTo(padding.left, height + padding.top); - ctx.strokeStyle = '#D2DAE0'; - ctx.lineWidth = 2; - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(0, height + padding.top); - ctx.lineTo(padding.left, height + padding.top); - ctx.stroke(); - const yMax = Math.round(Math.max(...data.map((d) => +d.acml_vol)) * 1.2); const yMin = Math.round(Math.min(...data.map((d) => +d.acml_vol)) * 0.8); - const labels = makeYLabels(yMax, yMin, 2); + const labels = makeYLabels(yMax, yMin, labelsNum); ctx.font = '24px sans-serif'; ctx.fillStyle = '#000'; ctx.textAlign = 'left'; @@ -41,13 +30,8 @@ export const drawLowerYAxis = ( const valueRatio = (label - yMin) / (yMax - yMin); const yPos = height - valueRatio * height; const formattedValue = formatNumber(label); - ctx.moveTo(0, yPos + padding.top); - ctx.lineTo(padding.left, yPos + padding.top); ctx.fillText(formattedValue, width / 2 + padding.left, yPos + padding.top); }); - ctx.strokeStyle = '#D2DAE0'; - ctx.lineWidth = 2; - ctx.stroke(); }; const formatNumber = (value: number) => { diff --git a/FE/src/utils/chart/drawUpperYAxis.ts b/FE/src/utils/chart/drawUpperYAxis.ts index e6b1ae18..d7a1df78 100644 --- a/FE/src/utils/chart/drawUpperYAxis.ts +++ b/FE/src/utils/chart/drawUpperYAxis.ts @@ -6,6 +6,7 @@ export const drawUpperYAxis = ( data: StockChartUnit[], width: number, height: number, + labelsNum: number, padding: Padding, weight: number = 0, ) => { @@ -14,7 +15,6 @@ export const drawUpperYAxis = ( .flat(); const yMax = Math.round(Math.max(...values) * (1 + weight)); const yMin = Math.round(Math.min(...values) * (1 - weight)); - ctx.clearRect( 0, 0, @@ -22,8 +22,7 @@ export const drawUpperYAxis = ( height + padding.top + padding.bottom, ); - const labels = makeYLabels(yMax, yMin, 3); - + const labels = makeYLabels(yMax, yMin, labelsNum); ctx.font = '24px sans-serif'; ctx.fillStyle = '#000'; ctx.textAlign = 'left'; @@ -31,18 +30,9 @@ export const drawUpperYAxis = ( ctx.beginPath(); labels.forEach((label) => { - const yPos = - padding.top + height - ((label - yMin) / (yMax - yMin)) * height; - + const valueRatio = (label - yMin) / (yMax - yMin); + const yPos = height - valueRatio * height; const formattedValue = label.toLocaleString(); - ctx.moveTo(0, yPos); - ctx.lineTo(padding.left, yPos); - ctx.fillText(formattedValue, width / 2 + padding.left, yPos); + ctx.fillText(formattedValue, width / 2 + padding.left, yPos + padding.top); }); - - ctx.moveTo(padding.left, 0); - ctx.lineTo(padding.left, height + padding.top + padding.bottom); - ctx.strokeStyle = '#D2DAE0'; - ctx.lineWidth = 2; - ctx.stroke(); }; diff --git a/FE/src/utils/chart/makeLabels.ts b/FE/src/utils/chart/makeLabels.ts index a2f20e81..652a5f90 100644 --- a/FE/src/utils/chart/makeLabels.ts +++ b/FE/src/utils/chart/makeLabels.ts @@ -8,7 +8,7 @@ export const makeYLabels = ( const labels = []; const rawTickInterval = Math.ceil((yMax - yMin) / divideNumber); const magnitude = 10 ** (String(rawTickInterval).length - 1); - const tickInterval = Math.floor(rawTickInterval / magnitude) * magnitude; + const tickInterval = Math.round(rawTickInterval / magnitude) * magnitude; const startValue = Math.ceil(yMin / tickInterval) * tickInterval; for (let value = startValue; value <= yMax; value += tickInterval) { From 7fcbe9a31ae8368c7d419fc1b7d242a731652b89 Mon Sep 17 00:00:00 2001 From: Seo San Date: Wed, 20 Nov 2024 17:35:21 +0900 Subject: [PATCH 41/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EB=86=92=EC=9D=B4=20=EC=A1=B0=EC=A0=88=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EB=9D=BC=EB=B2=A8=20=EA=B0=AF=EC=88=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20#123=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=EA=B0=80=20=EC=A1=B0=EC=A0=88=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=86=92=EC=9D=B4=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EC=9D=98=20=EA=B0=AF=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=A1=B0=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 36 +++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index b1250200..ac6b1bb4 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -13,7 +13,6 @@ import { drawBarChart } from 'utils/chart/drawBarChart.ts'; import { drawXAxis } from 'utils/chart/drawXAxis.ts'; import { drawUpperYAxis } from 'utils/chart/drawUpperYAxis.ts'; import { drawLowerYAxis } from 'utils/chart/drawLowerYAxis.ts'; -import { useDimensionsHook } from './useDimensionsHook.ts'; import { drawChartGrid } from 'utils/chart/drawChartGrid.ts'; const categories: { label: string; value: TiemCategory }[] = [ @@ -51,7 +50,8 @@ export default function Chart({ code }: StocksDeatailChartProps) { xAxisHeight: 0.1, }); const [isDragging, setIsDragging] = useState(false); - const dimension = useDimensionsHook(containerRef); + const [upperLabelNum, setUpperLabelNum] = useState(3); + const [lowerLabelNum, setLowerLabelNum] = useState(3); const { data, isLoading } = useQuery( ['stocksChartData', code, timeCategory], @@ -68,20 +68,27 @@ export default function Chart({ code }: StocksDeatailChartProps) { const minHeight = 0.2; const containerRect = containerRef.current.getBoundingClientRect(); const mouseY = e.clientY - containerRect.top; - - let ratio = mouseY / dimension.height; - + let ratio = mouseY / containerRef.current.clientHeight; const maxHeight = 0.9 - minHeight; - const upperRatio = Math.min(maxHeight, Math.max(minHeight, ratio)); const lowerRatio = 0.9 - upperRatio; + const calculateLabelNum = (ratio: number) => { + if (ratio <= 0.2) return 1; + if (ratio <= 0.35) return 2; + if (ratio <= 0.55) return 3; + return 4; + }; + if (lowerRatio >= minHeight && upperRatio >= minHeight) { setChartSizeConfig((prev) => ({ ...prev, upperHeight: upperRatio, lowerHeight: lowerRatio, })); + + setUpperLabelNum(calculateLabelNum(upperRatio)); + setLowerLabelNum(calculateLabelNum(lowerRatio)); } }; @@ -105,11 +112,12 @@ export default function Chart({ code }: StocksDeatailChartProps) { widthConfig: number, heightConfig: number, ) => { - canvas.width = dimension.width * widthConfig * 2; - canvas.height = dimension.height * heightConfig * 2; + if (!containerRef.current) return; + canvas.width = containerRef.current.clientWidth * widthConfig * 2; + canvas.height = containerRef.current.clientHeight * heightConfig * 2; - canvas.style.width = `${dimension.width * widthConfig}px`; - canvas.style.height = `${dimension.height * heightConfig}px`; + canvas.style.width = `${containerRef.current.clientWidth * widthConfig}px`; + canvas.style.height = `${containerRef.current.clientHeight * heightConfig}px`; }; const renderChart = ( @@ -139,11 +147,11 @@ export default function Chart({ code }: StocksDeatailChartProps) { UpperChartCtx, upperChartCanvas.width - padding.left - padding.right, upperChartCanvas.height - padding.top - padding.bottom, - 3, + upperLabelNum, LowerChartCtx, lowerChartCanvas.width - padding.left - padding.right, lowerChartCanvas.height - padding.top - padding.bottom, - 2, + lowerLabelNum, chartData, padding, ); @@ -183,7 +191,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { chartData, upperChartYCanvas.width - padding.left - padding.right, upperChartYCanvas.height - padding.top - padding.bottom, - 3, + upperLabelNum, padding, 0.1, ); @@ -193,7 +201,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { chartData, lowerChartYCanvas.width - padding.left - padding.right, lowerChartYCanvas.height - padding.top - padding.bottom, - 2, + lowerLabelNum, padding, ); From 2dec6bd516365ff4714d1c2896bb8a203ff469db Mon Sep 17 00:00:00 2001 From: Seo San Date: Wed, 20 Nov 2024 17:35:51 +0900 Subject: [PATCH 42/77] =?UTF-8?q?=F0=9F=94=A5=20remove:=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=ED=9B=85=20=EC=A0=9C=EA=B1=B0.?= =?UTF-8?q?=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StocksDetail/useDimensionsHook.ts | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 FE/src/components/StocksDetail/useDimensionsHook.ts diff --git a/FE/src/components/StocksDetail/useDimensionsHook.ts b/FE/src/components/StocksDetail/useDimensionsHook.ts deleted file mode 100644 index 14e18c7a..00000000 --- a/FE/src/components/StocksDetail/useDimensionsHook.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { RefObject, useCallback, useEffect, useState } from 'react'; - -type ChartDimensions = { - width: number; - height: number; -}; - -export const useDimensionsHook = (containerRef: RefObject) => { - const [dimensions, setDimensions] = useState({ - width: 0, - height: 0, - }); - - const updateDimensions = useCallback(() => { - if (!containerRef.current) return; - - setDimensions({ - width: containerRef.current.clientWidth, - height: containerRef.current.clientHeight, - }); - }, []); - - useEffect(() => { - updateDimensions(); - }, [updateDimensions, containerRef]); - - return dimensions; -}; From 7aa8ff68e3a3ce91159ed35fc497c4e4547b625e Mon Sep 17 00:00:00 2001 From: dongree Date: Wed, 20 Nov 2024 17:37:37 +0900 Subject: [PATCH 43/77] =?UTF-8?q?=E2=9C=A8=20feat:=20mypage=20routing=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20#154?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/App.tsx | 2 ++ FE/src/components/Header.tsx | 4 ++-- FE/src/page/MyPage.tsx | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 FE/src/page/MyPage.tsx diff --git a/FE/src/App.tsx b/FE/src/App.tsx index d3d0e74d..13539da2 100644 --- a/FE/src/App.tsx +++ b/FE/src/App.tsx @@ -10,6 +10,7 @@ import StocksDetail from 'page/StocksDetail'; import Header from 'components/Header'; import Login from 'components/Login'; import SearchModal from './components/Search'; +import MyPage from 'page/MyPage'; function App() { return ( @@ -18,6 +19,7 @@ function App() { }> } /> } /> + } /> diff --git a/FE/src/components/Header.tsx b/FE/src/components/Header.tsx index f6010ba7..23eabf63 100644 --- a/FE/src/components/Header.tsx +++ b/FE/src/components/Header.tsx @@ -21,9 +21,9 @@ export default function Header() {
mypage
; +} From 43bf2e51f59e95fcfb73fed941eb6ec66cb4167e Mon Sep 17 00:00:00 2001 From: dongree Date: Wed, 20 Nov 2024 18:10:59 +0900 Subject: [PATCH 44/77] =?UTF-8?q?=E2=9C=A8=20feat:=20mypage=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1=20#154?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Mypage/Nav.tsx | 30 ++++++++++++++++++++++++++++++ FE/src/page/MyPage.tsx | 15 ++++++++++++++- FE/src/types.ts | 2 ++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 FE/src/components/Mypage/Nav.tsx diff --git a/FE/src/components/Mypage/Nav.tsx b/FE/src/components/Mypage/Nav.tsx new file mode 100644 index 00000000..094f1729 --- /dev/null +++ b/FE/src/components/Mypage/Nav.tsx @@ -0,0 +1,30 @@ +import { useSearchParams } from 'react-router-dom'; +import { MypageSectionType } from 'types'; + +const mapping = { + account: '보유 자산 현황', + info: '내 정보', +}; +const sections: MypageSectionType[] = ['account', 'info']; + +export default function Nav() { + const [searchParams, setSearchParams] = useSearchParams(); + const currentSection = searchParams.get('section') || 'account'; + + const handleClick = (section: MypageSectionType) => { + setSearchParams({ section }); + }; + + return ( +
+ {sections.map((e) => ( + + ))} +
+ ); +} diff --git a/FE/src/page/MyPage.tsx b/FE/src/page/MyPage.tsx index 65618130..3d925145 100644 --- a/FE/src/page/MyPage.tsx +++ b/FE/src/page/MyPage.tsx @@ -1,3 +1,16 @@ +import Nav from 'components/Mypage/Nav'; +import { useSearchParams } from 'react-router-dom'; + export default function MyPage() { - return
mypage
; + const [searchParams] = useSearchParams(); + const currentPage = searchParams.get('section') || 'account'; + + return ( +
+
+ ); } diff --git a/FE/src/types.ts b/FE/src/types.ts index a2b97e89..08a8dcbc 100644 --- a/FE/src/types.ts +++ b/FE/src/types.ts @@ -39,3 +39,5 @@ export type StockChartUnit = { acml_vol: string; prdy_vrss_sign: string; }; + +export type MypageSectionType = 'account' | 'info'; From 7968b1a03826ca2b8a87dfb224084fa689836bb6 Mon Sep 17 00:00:00 2001 From: Seo San Date: Wed, 20 Nov 2024 18:20:43 +0900 Subject: [PATCH 45/77] =?UTF-8?q?=E2=9C=A8=20feat:=20X=EC=B6=95=20?= =?UTF-8?q?=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EC=83=9D=EC=84=B1=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/utils/chart/drawChartGrid.ts | 47 ++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/FE/src/utils/chart/drawChartGrid.ts b/FE/src/utils/chart/drawChartGrid.ts index 8206702e..2d91399f 100644 --- a/FE/src/utils/chart/drawChartGrid.ts +++ b/FE/src/utils/chart/drawChartGrid.ts @@ -1,5 +1,5 @@ import { Padding, StockChartUnit } from '../../types.ts'; -import { makeYLabels } from './makeLabels.ts'; +import { makeXLabels, makeYLabels } from './makeLabels.ts'; export const drawChartGrid = ( upperChartCtx: CanvasRenderingContext2D, @@ -13,6 +13,37 @@ export const drawChartGrid = ( data: StockChartUnit[], padding: Padding, ) => { + const xAxisLabels = makeXLabels(data); + const barWidth = Math.floor(lowerChartWidth / data.length); + lowerChartCtx.beginPath(); + upperChartCtx.beginPath(); + data.forEach((item, i) => { + if (xAxisLabels.includes(item.stck_bsop_date) || i === data.length - 1) { + lowerChartCtx.moveTo( + padding.left + (lowerChartWidth * i) / (data.length - 1) + barWidth / 2, + 0, + ); + lowerChartCtx.lineTo( + padding.left + (lowerChartWidth * i) / (data.length - 1) + barWidth / 2, + lowerChartHeight + padding.bottom, + ); + upperChartCtx.moveTo( + padding.left + (upperChartWidth * i) / (data.length - 1) + barWidth / 2, + padding.top, + ); + upperChartCtx.lineTo( + padding.left + (upperChartWidth * i) / (data.length - 1) + barWidth / 2, + upperChartHeight + padding.top + padding.bottom, + ); + } + }); + lowerChartCtx.strokeStyle = '#D2DAE0'; + lowerChartCtx.lineWidth = 1; + lowerChartCtx.stroke(); + upperChartCtx.strokeStyle = '#D2DAE0'; + upperChartCtx.lineWidth = 1; + upperChartCtx.stroke(); + const volumes = data.map((d) => +d.acml_vol); const volumeMax = Math.round(Math.max(...volumes) * 1.2); const volumeMin = Math.round(Math.min(...volumes) * 0.8); @@ -38,22 +69,22 @@ export const drawChartGrid = ( ); lowerChartCtx.lineTo(lowerChartWidth + padding.left + padding.right, 0); lowerChartCtx.strokeStyle = '#D2DAE0'; - lowerChartCtx.lineWidth = 2; + lowerChartCtx.lineWidth = 1; lowerChartCtx.stroke(); const values = data .map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc]) .flat(); - const yMax = Math.round(Math.max(...values) * (1 + 0.1)); - const yMin = Math.round(Math.min(...values) * (1 - 0.1)); + const valueMax = Math.round(Math.max(...values) * (1 + 0.1)); + const valueMin = Math.round(Math.min(...values) * (1 - 0.1)); - const labels = makeYLabels(yMax, yMin, upperLabelsNum); + const upperLabels = makeYLabels(valueMax, valueMin, upperLabelsNum); upperChartCtx.beginPath(); - labels.forEach((label) => { + upperLabels.forEach((label) => { const yPos = padding.top + upperChartHeight - - ((label - yMin) / (yMax - yMin)) * upperChartHeight; + ((label - valueMin) / (valueMax - valueMin)) * upperChartHeight; upperChartCtx.moveTo(0, yPos); upperChartCtx.lineTo(upperChartWidth + padding.left + padding.right, yPos); }); @@ -66,6 +97,6 @@ export const drawChartGrid = ( padding.top, ); upperChartCtx.strokeStyle = '#D2DAE0'; - upperChartCtx.lineWidth = 2; + upperChartCtx.lineWidth = 1; upperChartCtx.stroke(); }; From ab5bc601cc2c489c393694d141b7164562522cc1 Mon Sep 17 00:00:00 2001 From: Seo San Date: Wed, 20 Nov 2024 18:22:56 +0900 Subject: [PATCH 46/77] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20YYYY.MM.?= =?UTF-8?q?DD=20=ED=98=95=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=ED=8F=AC=EB=A9=94?= =?UTF-8?q?=ED=8C=85=ED=95=98=EB=8A=94=20=ED=95=A8=EC=88=98=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC,=20=EC=9E=AC=EC=82=AC=EC=9A=A9,=20=EB=A7=88=EC=9A=B0?= =?UTF-8?q?=EC=8A=A4=20=EC=9C=84=EC=B9=98=20=ED=95=A8=EC=88=98=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B2=98=EB=A6=AC=20#123=20=EB=82=A0=EC=A7=9C=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=EC=9D=84=20=ED=8F=AC=EB=A9=94=ED=8C=85?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=95=A8=EC=88=98=EB=A5=BC=20PriceTableDa?= =?UTF-8?q?yCard.tsx=EC=97=90=EC=84=9C=20=EB=B6=84=EB=A6=AC=20=ED=9B=84=20?= =?UTF-8?q?=EC=9E=AC=EC=82=AC=EC=9A=A9.=20=EC=BC=84=EB=B2=84=EC=8A=A4=20?= =?UTF-8?q?=EB=82=B4=EC=97=90=EC=84=9C=20=EB=A7=88=EC=9A=B0=EC=8A=A4=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=EB=A5=BC=20=EC=B0=BE=EB=8A=94=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EC=95=84=EC=A7=81=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EA=B8=B0=20=EB=95=8C=EB=AC=B8?= =?UTF-8?q?=EC=97=90=20=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 17 ++++++++++++++++- .../StocksDetail/PriceTableDayCard.tsx | 8 +------- FE/src/utils/chart/drawXAxis.ts | 5 +++-- FE/src/utils/formatTime.ts | 7 +++++++ 4 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 FE/src/utils/formatTime.ts diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index ac6b1bb4..60e95bd6 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -96,6 +96,17 @@ export default function Chart({ code }: StocksDeatailChartProps) { setIsDragging(false); }; + // const getCanvasMousePosition = (e: MouseEvent) => { + // if (!containerRef.current) return; + // const rect = containerRef.current.getBoundingClientRect(); + // const tmp = { + // x:e.clientX - rect.left, + // y: e.clientY - rect.top, + // }; + // console.log(tmp); + // return tmp; + // }; + useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); @@ -288,7 +299,11 @@ export default function Chart({ code }: StocksDeatailChartProps) { ))}
-
+
{/* Upper 차트 영역 */}
diff --git a/FE/src/components/StocksDetail/PriceTableDayCard.tsx b/FE/src/components/StocksDetail/PriceTableDayCard.tsx index a7f08e14..62bde95a 100644 --- a/FE/src/components/StocksDetail/PriceTableDayCard.tsx +++ b/FE/src/components/StocksDetail/PriceTableDayCard.tsx @@ -1,4 +1,5 @@ import { DailyPriceDataType } from './PriceDataType.ts'; +import { formatTime } from '../../utils/formatTime.ts'; type PriceTableDayCardProps = { data: DailyPriceDataType; @@ -15,13 +16,6 @@ export default function PriceTableDayCard({ data }: PriceTableDayCardProps) { const plusOrMinus = data.prdy_vrss_sign === '3' ? '' : data.prdy_vrss_sign < '3' ? '+' : '-'; - function formatTime(time: string) { - if (!time.length) return '----.--.--'; - const year = time.slice(0, 4); - const mon = time.slice(4, 6); - const day = time.slice(6, 8); - return `${year}.${mon}.${day}`; - } return ( diff --git a/FE/src/utils/chart/drawXAxis.ts b/FE/src/utils/chart/drawXAxis.ts index 15b754d5..8d9943ff 100644 --- a/FE/src/utils/chart/drawXAxis.ts +++ b/FE/src/utils/chart/drawXAxis.ts @@ -1,5 +1,6 @@ -import { Padding, StockChartUnit } from '../../types.ts'; +import { Padding, StockChartUnit } from 'types.ts'; import { makeXLabels } from './makeLabels.ts'; +import { formatTime } from '../formatTime.ts'; export const drawXAxis = ( ctx: CanvasRenderingContext2D, @@ -25,7 +26,7 @@ export const drawXAxis = ( data.forEach((item, i) => { if (labels.includes(item.stck_bsop_date) || i === data.length - 1) { ctx.fillText( - item.stck_bsop_date, + formatTime(item.stck_bsop_date), padding.left + (width * i) / (data.length - 1) + barWidth / 2, height / 2, ); diff --git a/FE/src/utils/formatTime.ts b/FE/src/utils/formatTime.ts new file mode 100644 index 00000000..87f476e5 --- /dev/null +++ b/FE/src/utils/formatTime.ts @@ -0,0 +1,7 @@ +export function formatTime(time: string) { + if (!time.length) return '----.--.--'; + const year = time.slice(0, 4); + const mon = time.slice(4, 6); + const day = time.slice(6, 8); + return `${year}.${mon}.${day}`; +} From 149baae743557095d2ddc4d07f8ad9657909f19c Mon Sep 17 00:00:00 2001 From: dongree Date: Wed, 20 Nov 2024 18:28:13 +0900 Subject: [PATCH 47/77] =?UTF-8?q?=E2=9C=A8=20feat:=20mypage=20=EC=9E=90?= =?UTF-8?q?=EC=82=B0=20=EC=83=81=EB=8B=A8=EB=B6=80=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84=20#154?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Mypage/Account.tsx | 29 ++++++++++++++++++++++++++++ FE/src/page/MyPage.tsx | 7 ++++--- 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 FE/src/components/Mypage/Account.tsx diff --git a/FE/src/components/Mypage/Account.tsx b/FE/src/components/Mypage/Account.tsx new file mode 100644 index 00000000..82f40b07 --- /dev/null +++ b/FE/src/components/Mypage/Account.tsx @@ -0,0 +1,29 @@ +export default function Account() { + return ( +
+
+
+
+

총 자산

+

7000000원

+
+
+

가용 자산

+

70000원

+
+
+
+
+

투자 손익

+

-1000000원

+
+
+

수익률

+

-30%

+
+
+
+
list
+
+ ); +} diff --git a/FE/src/page/MyPage.tsx b/FE/src/page/MyPage.tsx index 3d925145..5a9b2540 100644 --- a/FE/src/page/MyPage.tsx +++ b/FE/src/page/MyPage.tsx @@ -1,3 +1,4 @@ +import Account from 'components/Mypage/Account'; import Nav from 'components/Mypage/Nav'; import { useSearchParams } from 'react-router-dom'; @@ -6,10 +7,10 @@ export default function MyPage() { const currentPage = searchParams.get('section') || 'account'; return ( -
+
{ + setIsLogin(false); + deleteCookie('accessToken'); + }} > 로그아웃 diff --git a/FE/src/components/Login/index.tsx b/FE/src/components/Login/index.tsx index 8f6bbb35..91dec9f2 100644 --- a/FE/src/components/Login/index.tsx +++ b/FE/src/components/Login/index.tsx @@ -10,7 +10,7 @@ export default function Login() { const { isOpen, toggleModal } = useLoginModalStore(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const { setAccessToken } = useAuthStore(); + const { setIsLogin } = useAuthStore(); const [errorCode, setErrorCode] = useState(200); useEffect(() => { @@ -29,7 +29,7 @@ export default function Login() { return; } - setAccessToken(res.accessToken); + setIsLogin(true); toggleModal(); }; @@ -46,6 +46,7 @@ export default function Login() { } document.cookie = `accessToken=${res.accessToken}; path=/;`; + setIsLogin(true); toggleModal(); return; } diff --git a/FE/src/store/authStore.ts b/FE/src/store/authStore.ts index 1a8e8453..0e9a9494 100644 --- a/FE/src/store/authStore.ts +++ b/FE/src/store/authStore.ts @@ -4,12 +4,16 @@ type AuthStore = { accessToken: string | null; isLogin: boolean; setAccessToken: (token: string) => void; + setIsLogin: (isLogin: boolean) => void; resetToken: () => void; }; const useAuthStore = create((set) => ({ accessToken: null, isLogin: false, + setIsLogin: (isLogin: boolean) => { + set({ isLogin }); + }, setAccessToken: (token: string) => { set({ accessToken: token, isLogin: token !== null }); }, diff --git a/FE/src/utils/common.ts b/FE/src/utils/common.ts index 5e09b33b..632c429a 100644 --- a/FE/src/utils/common.ts +++ b/FE/src/utils/common.ts @@ -1,3 +1,8 @@ export function stringToLocaleString(s: string) { return (+s).toLocaleString(); } + +export function deleteCookie(name: string) { + document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; + window.location.reload(); +} From 53801703e67e79322c0fc1fa2bee5bd85870e1b9 Mon Sep 17 00:00:00 2001 From: dongree Date: Thu, 21 Nov 2024 14:07:58 +0900 Subject: [PATCH 61/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=ED=98=84=ED=99=A9=20layout=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20#156?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Mypage/Nav.tsx | 3 +- FE/src/components/Mypage/Order.tsx | 105 +++++++++++++++++++++++++++++ FE/src/page/MyPage.tsx | 9 ++- FE/src/types.ts | 5 +- 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 FE/src/components/Mypage/Order.tsx diff --git a/FE/src/components/Mypage/Nav.tsx b/FE/src/components/Mypage/Nav.tsx index fda936e0..a50c272b 100644 --- a/FE/src/components/Mypage/Nav.tsx +++ b/FE/src/components/Mypage/Nav.tsx @@ -3,9 +3,10 @@ import { MypageSectionType } from 'types'; const mapping = { account: '보유 자산 현황', + order: '주문 요청 현황', info: '내 정보', }; -const sections: MypageSectionType[] = ['account', 'info']; +const sections: MypageSectionType[] = ['account', 'order', 'info']; export default function Nav() { const [searchParams, setSearchParams] = useSearchParams(); diff --git a/FE/src/components/Mypage/Order.tsx b/FE/src/components/Mypage/Order.tsx new file mode 100644 index 00000000..af86780c --- /dev/null +++ b/FE/src/components/Mypage/Order.tsx @@ -0,0 +1,105 @@ +const orders = [ + { + stock_code: '11', + stock_name: '삼성전자', + price: 1111, + amount: 1, + trade_type: 'BUY', + create_at: '2024-11-21', + }, + { + stock_code: '11', + stock_name: '삼성전자', + price: 1111, + amount: 1, + trade_type: 'BUY', + create_at: '2024-11-21', + }, + { + stock_code: '11', + stock_name: '삼성전자', + price: 1111, + amount: 1, + trade_type: 'BUY', + create_at: '2024-11-21', + }, + { + stock_code: '11', + stock_name: '삼성전자', + price: 1111, + amount: 1, + trade_type: 'BUY', + create_at: '2024-11-21', + }, + { + stock_code: '11', + stock_name: '삼성전자', + price: 1111, + amount: 1, + trade_type: 'BUY', + create_at: '2024-11-21', + }, + { + stock_code: '11', + stock_name: '삼성전자', + price: 1111, + amount: 1, + trade_type: 'BUY', + create_at: '2024-11-21', + }, +]; + +export default function Order() { + return ( +
+
+

종목

+

요청 유형

+

수량

+

요청 가격

+

요청 시간

+

+
+ +
    + {orders.map((order) => { + const { + stock_code, + stock_name, + price, + amount, + trade_type, + create_at, + } = order; + + const handleCancelOrder = () => { + console.log(`Canceling order for ${stock_code}`); + }; + + return ( +
  • +
    +

    {stock_name}

    +

    {stock_code}

    +
    +

    + {trade_type === 'BUY' ? '매수' : '매도'} +

    +

    {amount}

    +

    {price.toLocaleString()}원

    +

    {create_at}

    +

    + +

    +
  • + ); + })} +
+
+ ); +} diff --git a/FE/src/page/MyPage.tsx b/FE/src/page/MyPage.tsx index 5a9b2540..a524ffda 100644 --- a/FE/src/page/MyPage.tsx +++ b/FE/src/page/MyPage.tsx @@ -1,5 +1,6 @@ import Account from 'components/Mypage/Account'; import Nav from 'components/Mypage/Nav'; +import Order from 'components/Mypage/Order'; import { useSearchParams } from 'react-router-dom'; export default function MyPage() { @@ -10,7 +11,13 @@ export default function MyPage() {
); diff --git a/FE/src/types.ts b/FE/src/types.ts index 2dd0a8a8..f5c11593 100644 --- a/FE/src/types.ts +++ b/FE/src/types.ts @@ -40,8 +40,7 @@ export type StockChartUnit = { prdy_vrss_sign: string; }; - -export type MypageSectionType = 'account' | 'info'; +export type MypageSectionType = 'account' | 'order' | 'info'; export type Asset = { cash_balance: string; @@ -68,4 +67,4 @@ export type ChartSizeConfigType = { chartWidth: number; yAxisWidth: number; xAxisHeight: number; -}; \ No newline at end of file +}; From 2de0a2cf737e0fb54502242342a67e4ca7edb8e2 Mon Sep 17 00:00:00 2001 From: Seo San Date: Thu, 21 Nov 2024 14:23:32 +0900 Subject: [PATCH 62/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=9E=AD=ED=82=B9=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B5=AC=ED=98=84=20#13?= =?UTF-8?q?2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Rank/Nav.tsx | 16 +++++++++++ FE/src/components/Rank/RankCard.tsx | 42 +++++++++++++++++++++++++++++ FE/src/components/Rank/RankList.tsx | 38 ++++++++++++++++++++++++++ FE/src/components/Rank/RankType.ts | 4 +++ FE/src/page/Rank.tsx | 40 +++++++++++++++++++++++++-- 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 FE/src/components/Rank/Nav.tsx create mode 100644 FE/src/components/Rank/RankCard.tsx create mode 100644 FE/src/components/Rank/RankList.tsx create mode 100644 FE/src/components/Rank/RankType.ts diff --git a/FE/src/components/Rank/Nav.tsx b/FE/src/components/Rank/Nav.tsx new file mode 100644 index 00000000..67138e12 --- /dev/null +++ b/FE/src/components/Rank/Nav.tsx @@ -0,0 +1,16 @@ +const RankingCategory: string[] = ['일간']; + +export default function Nav() { + return ( +
+ {RankingCategory.map((category) => ( + + ))} +
+ ); +} diff --git a/FE/src/components/Rank/RankCard.tsx b/FE/src/components/Rank/RankCard.tsx new file mode 100644 index 00000000..b2ac264b --- /dev/null +++ b/FE/src/components/Rank/RankCard.tsx @@ -0,0 +1,42 @@ +import { TmpDataType } from './RankType.ts'; + +type Props = { + item: TmpDataType; + ranking: number; + type: '수익률순' | '자산순'; +}; +export default function RankCard({ item, ranking, type }: Props) { + return ( +
+
+
+ {ranking + 1} +
+ {item.nickname} +
+
+ + {type === '수익률순' + ? `${item.value}%` + : new Intl.NumberFormat('ko-KR', { + notation: 'compact', + maximumFractionDigits: 1, + }).format(item.value) + '원'} + +
+
+ ); +} diff --git a/FE/src/components/Rank/RankList.tsx b/FE/src/components/Rank/RankList.tsx new file mode 100644 index 00000000..dc35d566 --- /dev/null +++ b/FE/src/components/Rank/RankList.tsx @@ -0,0 +1,38 @@ +import { TmpDataType } from './RankType.ts'; +import RankCard from './RankCard.tsx'; + +type Props = { + title: '수익률순' | '자산순'; + data: TmpDataType[]; +}; +export default function RankList({ title, data }: Props) { + return ( +
+
+
+

{title}

+
+ +
+ {data.map((item, index) => ( + + ))} +
+
+
+
+

{`내 ${title} 순위`}

+
+ +
+ +
+
+
+ ); +} diff --git a/FE/src/components/Rank/RankType.ts b/FE/src/components/Rank/RankType.ts new file mode 100644 index 00000000..bba826fa --- /dev/null +++ b/FE/src/components/Rank/RankType.ts @@ -0,0 +1,4 @@ +export type TmpDataType = { + nickname: string; + value: number; +}; diff --git a/FE/src/page/Rank.tsx b/FE/src/page/Rank.tsx index 4406032d..4da21abe 100644 --- a/FE/src/page/Rank.tsx +++ b/FE/src/page/Rank.tsx @@ -1,7 +1,43 @@ +import Nav from 'components/Rank/Nav.tsx'; +import RankList from '../components/Rank/RankList.tsx'; + +const dummyOne = [ + { nickname: 'MasterInvestor', value: 28.5 }, + { nickname: 'StockExpert', value: 22.3 }, + { nickname: 'SuperTrader', value: 19.8 }, + { nickname: 'WallStreetWolf', value: 17.2 }, + { nickname: 'WealthKing', value: 15.9 }, + { nickname: 'LuckyInvestor', value: 14.1 }, + { nickname: 'RichMaker', value: 12.8 }, + { nickname: 'InvestmentGuru', value: 11.5 }, + { nickname: 'MarketAnalyst', value: 10.2 }, + { nickname: 'FutureAsset', value: 9.7 }, +]; + +const dummyTwo = [ + { nickname: 'MasterInvestor', value: 15800000000 }, + { nickname: 'StockExpert', value: 9200000000 }, + { nickname: 'SuperTrader', value: 7500000000 }, + { nickname: 'WallStreetWolf', value: 6300000000 }, + { nickname: 'WealthKing', value: 4800000000 }, + { nickname: 'LuckyInvestor', value: 3200000000 }, + { nickname: 'RichMaker', value: 2500000000 }, + { nickname: 'InvestmentGuru', value: 1800000000 }, + { nickname: 'MarketAnalyst', value: 1200000000 }, + { nickname: 'FutureAsset', value: 950000000 }, +]; + export default function Rank() { return ( -
-

Ranking

+
+
+
+ +
+ + +
); } From ee620dbca8411b24ba0bd651233b4323e39bf2d8 Mon Sep 17 00:00:00 2001 From: dongree Date: Thu, 21 Nov 2024 14:32:07 +0900 Subject: [PATCH 63/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=ED=98=84=ED=99=A9=20api=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Mypage/Order.tsx | 66 ++++++------------------------ FE/src/service/orders.ts | 14 +++++++ FE/src/types.ts | 10 +++++ 3 files changed, 37 insertions(+), 53 deletions(-) create mode 100644 FE/src/service/orders.ts diff --git a/FE/src/components/Mypage/Order.tsx b/FE/src/components/Mypage/Order.tsx index af86780c..5eac1ce9 100644 --- a/FE/src/components/Mypage/Order.tsx +++ b/FE/src/components/Mypage/Order.tsx @@ -1,55 +1,15 @@ -const orders = [ - { - stock_code: '11', - stock_name: '삼성전자', - price: 1111, - amount: 1, - trade_type: 'BUY', - create_at: '2024-11-21', - }, - { - stock_code: '11', - stock_name: '삼성전자', - price: 1111, - amount: 1, - trade_type: 'BUY', - create_at: '2024-11-21', - }, - { - stock_code: '11', - stock_name: '삼성전자', - price: 1111, - amount: 1, - trade_type: 'BUY', - create_at: '2024-11-21', - }, - { - stock_code: '11', - stock_name: '삼성전자', - price: 1111, - amount: 1, - trade_type: 'BUY', - create_at: '2024-11-21', - }, - { - stock_code: '11', - stock_name: '삼성전자', - price: 1111, - amount: 1, - trade_type: 'BUY', - create_at: '2024-11-21', - }, - { - stock_code: '11', - stock_name: '삼성전자', - price: 1111, - amount: 1, - trade_type: 'BUY', - create_at: '2024-11-21', - }, -]; +import { useQuery } from '@tanstack/react-query'; +import { getOrders } from 'service/orders'; export default function Order() { + const { data, isLoading, isError } = useQuery(['account', 'order'], () => + getOrders(), + ); + + if (isLoading) return
loading
; + if (!data) return
No data
; + if (isError) return
error
; + return (
@@ -62,14 +22,14 @@ export default function Order() {
    - {orders.map((order) => { + {data.map((order) => { const { stock_code, stock_name, price, amount, trade_type, - create_at, + created_at, } = order; const handleCancelOrder = () => { @@ -87,7 +47,7 @@ export default function Order() {

    {amount}

    {price.toLocaleString()}원

    -

    {create_at}

    +

    {created_at}

총 주문 금액

{(+price + charge).toLocaleString()}원

diff --git a/FE/src/service/orders.ts b/FE/src/service/orders.ts index f4866b5a..5d533c4b 100644 --- a/FE/src/service/orders.ts +++ b/FE/src/service/orders.ts @@ -1,5 +1,28 @@ import { Order } from 'types'; +export async function orderBuyStock( + code: string, + price: number, + amount: number, +) { + const url = import.meta.env.PROD + ? `${import.meta.env.VITE_API_URL}/stocks/order/buy` + : '/api/stocks/order/buy'; + + return fetch(url, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + stock_code: code, + price, + amount, + }), + }); +} + export async function getOrders(): Promise { const url = import.meta.env.PROD ? `${import.meta.env.VITE_API_URL}/stocks/trade/list` diff --git a/FE/src/service/stocks.ts b/FE/src/service/stocks.ts index 070a72e0..9f5d4bb4 100644 --- a/FE/src/service/stocks.ts +++ b/FE/src/service/stocks.ts @@ -22,22 +22,3 @@ export async function getStocksChartDataByCode( }), }).then((res) => res.json()); } - -export async function buyStock(code: string, price: number, amount: number) { - const url = import.meta.env.PROD - ? `${import.meta.env.VITE_API_URL}/stocks/trade/buy` - : '/api/stocks/trade/buy'; - - return fetch(url, { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - stock_code: code, - price, - amount, - }), - }); -} From 0c3b45a21ab0d6717358dcece5be52192c135c9f Mon Sep 17 00:00:00 2001 From: dongree Date: Thu, 21 Nov 2024 14:48:26 +0900 Subject: [PATCH 65/77] =?UTF-8?q?=F0=9F=94=A7=20fix:=20trade=20->=20order?= =?UTF-8?q?=20endpoint=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/service/orders.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FE/src/service/orders.ts b/FE/src/service/orders.ts index 5d533c4b..1fb34622 100644 --- a/FE/src/service/orders.ts +++ b/FE/src/service/orders.ts @@ -25,8 +25,8 @@ export async function orderBuyStock( export async function getOrders(): Promise { const url = import.meta.env.PROD - ? `${import.meta.env.VITE_API_URL}/stocks/trade/list` - : '/api/stocks/trade/list'; + ? `${import.meta.env.VITE_API_URL}/stocks/order/list` + : '/api/stocks/order/list'; return fetch(url, { credentials: 'include', From 50db58b9d81d632a5aa6bb7fe096cc2a993ef7b1 Mon Sep 17 00:00:00 2001 From: dongree Date: Thu, 21 Nov 2024 14:54:56 +0900 Subject: [PATCH 66/77] =?UTF-8?q?=E2=9E=95=20add:=20flag=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=A1=9C=20=ED=88=AC=EC=9E=90=20=EC=88=98=EC=9D=B5=EB=A5=A0=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EB=8B=A4=EB=A5=B4=EA=B2=8C=20=EB=B3=B4?= =?UTF-8?q?=EC=97=AC=EC=A3=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Mypage/AccountCondition.tsx | 23 +++++++++++++------ FE/src/types.ts | 1 + 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/FE/src/components/Mypage/AccountCondition.tsx b/FE/src/components/Mypage/AccountCondition.tsx index 64841d59..6e6aae33 100644 --- a/FE/src/components/Mypage/AccountCondition.tsx +++ b/FE/src/components/Mypage/AccountCondition.tsx @@ -12,6 +12,7 @@ export default function AccountCondition({ asset }: AccountConditionProps) { total_asset, total_profit, total_profit_rate, + is_positive, } = asset; return ( @@ -19,17 +20,17 @@ export default function AccountCondition({ asset }: AccountConditionProps) {

자산 현황

-

총 자산

+

총 자산

{stringToLocaleString(total_asset)}원

-

가용 자산

+

가용 자산

{stringToLocaleString(cash_balance)}원

-

주식 자산

+

주식 자산

{stringToLocaleString(stock_balance)}원

@@ -39,12 +40,20 @@ export default function AccountCondition({ asset }: AccountConditionProps) {

투자 성과

-

투자 손익

-

{stringToLocaleString(total_profit)}원

+

투자 손익

+

+ {stringToLocaleString(total_profit)}원 +

-

수익률

-

{total_profit_rate}%

+

수익률

+

+ {total_profit_rate}% +

diff --git a/FE/src/types.ts b/FE/src/types.ts index 18c3b03f..a99a3252 100644 --- a/FE/src/types.ts +++ b/FE/src/types.ts @@ -48,6 +48,7 @@ export type Asset = { total_asset: string; total_profit: string; total_profit_rate: string; + is_positive: boolean; }; export type MyStockListUnit = { From d572d2557048171826ba09ccd2baf85256166df2 Mon Sep 17 00:00:00 2001 From: Seo San Date: Thu, 21 Nov 2024 14:56:10 +0900 Subject: [PATCH 67/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=86=92=EC=9D=B4=20=EC=88=98=EC=A0=95=20#132=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EB=86=92=EC=9D=B4=EB=A5=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=95=9C=20=ED=99=94=EB=A9=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B3=B4=EC=9D=BC=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Rank/RankList.tsx | 12 ++++++------ FE/src/page/Rank.tsx | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/FE/src/components/Rank/RankList.tsx b/FE/src/components/Rank/RankList.tsx index dc35d566..06ceecb4 100644 --- a/FE/src/components/Rank/RankList.tsx +++ b/FE/src/components/Rank/RankList.tsx @@ -8,12 +8,12 @@ type Props = { export default function RankList({ title, data }: Props) { return (
-
-
+
+

{title}

-
+
{data.map((item, index) => (
-
-
+
+

{`내 ${title} 순위`}

-
+
diff --git a/FE/src/page/Rank.tsx b/FE/src/page/Rank.tsx index 4da21abe..1ef0604b 100644 --- a/FE/src/page/Rank.tsx +++ b/FE/src/page/Rank.tsx @@ -29,12 +29,12 @@ const dummyTwo = [ export default function Rank() { return ( -
-
+
+
-
+
From 306415f10037f2d0a7bb39bdb53f29da8f943184 Mon Sep 17 00:00:00 2001 From: Seo San Date: Thu, 21 Nov 2024 15:39:57 +0900 Subject: [PATCH 68/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=A7=88=EC=9A=B0?= =?UTF-8?q?=EC=8A=A4=20=EC=9C=84=EC=B9=98=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EA=B2=A9=EC=9E=90=20=EC=83=9D=EC=84=B1=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 51 +++++++++++---- FE/src/utils/chart/drawMouseGrid.ts | 81 ++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 FE/src/utils/chart/drawMouseGrid.ts diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index b14a28d8..51f6aae7 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -14,6 +14,7 @@ import { drawXAxis } from 'utils/chart/drawXAxis.ts'; import { drawUpperYAxis } from 'utils/chart/drawUpperYAxis.ts'; import { drawLowerYAxis } from 'utils/chart/drawLowerYAxis.ts'; import { drawChartGrid } from 'utils/chart/drawChartGrid.ts'; +import { drawMouseGrid } from '../../utils/chart/drawMouseGrid.ts'; const categories: { label: string; value: TiemCategory }[] = [ { label: '일', value: 'D' }, @@ -33,6 +34,11 @@ type StocksDeatailChartProps = { code: string; }; +export type MousePositionType = { + x: number; + y: number; +}; + export default function Chart({ code }: StocksDeatailChartProps) { const containerRef = useRef(null); const upperChartCanvasRef = useRef(null); @@ -52,6 +58,10 @@ export default function Chart({ code }: StocksDeatailChartProps) { const [isDragging, setIsDragging] = useState(false); const [upperLabelNum, setUpperLabelNum] = useState(3); const [lowerLabelNum, setLowerLabelNum] = useState(3); + const [mousePosition, setMousePosition] = useState({ + x: 0, + y: 0, + }); const { data, isLoading } = useQuery( ['stocksChartData', code, timeCategory], @@ -105,16 +115,14 @@ export default function Chart({ code }: StocksDeatailChartProps) { setIsDragging(false); }, []); - // const getCanvasMousePosition = (e: MouseEvent) => { - // if (!containerRef.current) return; - // const rect = containerRef.current.getBoundingClientRect(); - // const tmp = { - // x:e.clientX - rect.left, - // y: e.clientY - rect.top, - // }; - // console.log(tmp); - // return tmp; - // }; + const getCanvasMousePosition = (e: MouseEvent) => { + if (!containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + setMousePosition({ + x: (e.clientX - rect.left) * 2, + y: (e.clientY - rect.top) * 2, + }); + }; useEffect(() => { if (isDragging) { @@ -147,6 +155,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { lowerChartYCanvas: HTMLCanvasElement, chartXCanvas: HTMLCanvasElement, chartData: StockChartUnit[], + mousePosition: MousePositionType, ) => { const UpperChartCtx = upperChartCanvas.getContext('2d'); const LowerChartCtx = lowerChartCanvas.getContext('2d'); @@ -232,6 +241,24 @@ export default function Chart({ code }: StocksDeatailChartProps) { chartXCanvas.height, padding, ); + + if ( + mousePosition.x > padding.left && + mousePosition.x < upperChartCanvas.width && + mousePosition.y > padding.top && + mousePosition.y < upperChartCanvas.height + lowerChartCanvas.height + ) { + drawMouseGrid( + UpperChartCtx, + upperChartCanvas.width - padding.left - padding.right, + upperChartCanvas.height - padding.top - padding.bottom, + LowerChartCtx, + lowerChartCanvas.width - padding.left - padding.right, + lowerChartCanvas.height - padding.top - padding.bottom, + padding, + mousePosition, + ); + } }, [ padding, @@ -295,6 +322,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { lowerChartY.current, chartX.current, data, + mousePosition, ); }, [ timeCategory, @@ -303,6 +331,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { setCanvasSize, renderChart, charSizeConfig, + mousePosition, ]); return ( @@ -324,7 +353,7 @@ export default function Chart({ code }: StocksDeatailChartProps) {
{/* Upper 차트 영역 */}
diff --git a/FE/src/utils/chart/drawMouseGrid.ts b/FE/src/utils/chart/drawMouseGrid.ts new file mode 100644 index 00000000..01d55441 --- /dev/null +++ b/FE/src/utils/chart/drawMouseGrid.ts @@ -0,0 +1,81 @@ +import { Padding } from 'types.ts'; +import { MousePositionType } from 'components/StocksDetail/Chart.tsx'; + +export const drawMouseGrid = ( + upperChartCtx: CanvasRenderingContext2D, + upperChartWidth: number, + upperChartHeight: number, + lowerChartCtx: CanvasRenderingContext2D, + lowerChartWidth: number, + lowerChartHeight: number, + padding: Padding, + mousePosition: MousePositionType, +) => { + if ( + mousePosition.x > 0 && + mousePosition.x < upperChartWidth + padding.left + padding.right + ) { + upperChartCtx.beginPath(); + upperChartCtx.setLineDash([10, 10]); + upperChartCtx.moveTo(mousePosition.x, padding.top); + upperChartCtx.lineTo( + mousePosition.x, + padding.top + upperChartHeight + padding.bottom, + ); + + upperChartCtx.strokeStyle = '#6E8091'; + upperChartCtx.lineWidth = 1; + upperChartCtx.stroke(); + + lowerChartCtx.beginPath(); + lowerChartCtx.setLineDash([10, 10]); + lowerChartCtx.moveTo(mousePosition.x, 0); + lowerChartCtx.lineTo(mousePosition.x, upperChartHeight + padding.bottom); + + lowerChartCtx.strokeStyle = '#6E8091'; + lowerChartCtx.lineWidth = 1; + lowerChartCtx.stroke(); + } + + if ( + mousePosition.y > 0 && + mousePosition.y < upperChartHeight + padding.top + padding.bottom + ) { + upperChartCtx.beginPath(); + upperChartCtx.moveTo(0, mousePosition.y); + upperChartCtx.lineTo( + upperChartWidth + padding.left + padding.right, + mousePosition.y, + ); + + upperChartCtx.strokeStyle = '#6E8091'; + upperChartCtx.lineWidth = 1; + upperChartCtx.stroke(); + } + + if ( + mousePosition.y > upperChartHeight + padding.top + padding.bottom && + mousePosition.y < + upperChartHeight + + padding.top + + padding.bottom + + lowerChartHeight + + padding.top + + padding.bottom + ) { + lowerChartCtx.beginPath(); + lowerChartCtx.moveTo( + 0, + mousePosition.y - (upperChartHeight + padding.top + padding.bottom), + ); + lowerChartCtx.lineTo( + lowerChartWidth + padding.left + padding.right, + mousePosition.y - + (upperChartHeight + padding.top + padding.bottom + padding.bottom), + ); + + lowerChartCtx.strokeStyle = '#6E8091'; + lowerChartCtx.lineWidth = 1; + lowerChartCtx.stroke(); + } +}; From 75d4bd34103ccb099dbffcf89944af406f3d28bd Mon Sep 17 00:00:00 2001 From: dongree Date: Thu, 21 Nov 2024 15:14:25 +0900 Subject: [PATCH 69/77] =?UTF-8?q?=E2=9C=A8=20feat:=20=EC=A3=BC=EB=AC=B8=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=B7=A8=EC=86=8C=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20&=20=EB=A6=AC=EC=95=A1=ED=8A=B8=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=BB=A4=EC=8A=A4=ED=85=80=20hooks=EB=A1=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20#138?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Mypage/Order.tsx | 22 +++++++++++----------- FE/src/hooks/useOrder.ts | 14 ++++++++++++++ FE/src/service/orders.ts | 14 ++++++++++++++ FE/src/types.ts | 1 + 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 FE/src/hooks/useOrder.ts diff --git a/FE/src/components/Mypage/Order.tsx b/FE/src/components/Mypage/Order.tsx index 5eac1ce9..cdfcea2a 100644 --- a/FE/src/components/Mypage/Order.tsx +++ b/FE/src/components/Mypage/Order.tsx @@ -1,15 +1,18 @@ -import { useQuery } from '@tanstack/react-query'; -import { getOrders } from 'service/orders'; +import useOrders from 'hooks/useOrder'; export default function Order() { - const { data, isLoading, isError } = useQuery(['account', 'order'], () => - getOrders(), - ); + const { orderQuery, removeOrder } = useOrders(); + + const { data, isLoading, isError } = orderQuery; if (isLoading) return
loading
; if (!data) return
No data
; if (isError) return
error
; + const handleCancelOrder = (id: number) => { + removeOrder.mutate(id); + }; + return (
@@ -24,6 +27,7 @@ export default function Order() {
    {data.map((order) => { const { + id, stock_code, stock_name, price, @@ -32,12 +36,8 @@ export default function Order() { created_at, } = order; - const handleCancelOrder = () => { - console.log(`Canceling order for ${stock_code}`); - }; - return ( -
  • +
  • {stock_name}

    {stock_code}

    @@ -50,7 +50,7 @@ export default function Order() {

    {created_at}

    + + {isOpen && ( + + )} + + ); +} diff --git a/FE/src/components/StocksDetail/SellSection.tsx b/FE/src/components/StocksDetail/SellSection.tsx new file mode 100644 index 00000000..b34136ee --- /dev/null +++ b/FE/src/components/StocksDetail/SellSection.tsx @@ -0,0 +1,15 @@ +import Lottie from 'lottie-react'; +import emptyAnimation from 'assets/emptyAnimation.json'; + +export default function SellSection() { + return ( +

    + +

    매도할 주식이 없어요

    +
    + ); +} diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index cf8d322d..36bbb19b 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -1,39 +1,19 @@ -import Lottie from 'lottie-react'; -import { - ChangeEvent, - FocusEvent, - FormEvent, - useEffect, - useRef, - useState, -} from 'react'; -import emptyAnimation from 'assets/emptyAnimation.json'; +import { useEffect, useRef, useState } from 'react'; import { StockDetailType } from 'types'; -import useTradeAlertModalStore from 'store/tradeAlertModalStore'; -import TradeAlertModal from './TradeAlertModal'; + +import BuySection from './buySection'; +import SellSection from './SellSection'; type TradeSectionProps = { code: string; data: StockDetailType; }; -const MyAsset = 10000000; - export default function TradeSection({ code, data }: TradeSectionProps) { - const { stck_prpr, stck_mxpr, stck_llam } = data; - const [category, setCategory] = useState<'buy' | 'sell'>('buy'); - const [currPrice, setCurrPrice] = useState(stck_prpr); - const [upperLimitFlag, setUpperLimitFlag] = useState(false); - const [lowerLimitFlag, setLowerLimitFlag] = useState(false); - const [lackAssetFlag, setLackAssetFlag] = useState(false); - const { isOpen, toggleModal } = useTradeAlertModalStore(); - - const [count, setCount] = useState(0); const indicatorRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); - const timerRef = useRef(null); useEffect(() => { const idx = category === 'buy' ? 0 : 1; @@ -46,56 +26,6 @@ export default function TradeSection({ code, data }: TradeSectionProps) { } }, [category]); - const handlePriceChange = (e: ChangeEvent) => { - if (!isNumericString(e.target.value)) return; - - setCurrPrice(e.target.value); - }; - - const handlePriceInputBlur = (e: FocusEvent) => { - const n = +e.target.value; - if (n > +stck_mxpr) { - if (timerRef.current) { - clearTimeout(timerRef.current); - } - setCurrPrice(stck_mxpr); - - setUpperLimitFlag(true); - timerRef.current = window.setTimeout(() => { - setUpperLimitFlag(false); - }, 2000); - return; - } - - if (n < +stck_llam) { - if (timerRef.current) { - clearTimeout(timerRef.current); - } - setCurrPrice(stck_llam); - - setLowerLimitFlag(true); - timerRef.current = window.setTimeout(() => { - setLowerLimitFlag(false); - }, 2000); - return; - } - }; - - const handleBuy = async (e: FormEvent) => { - e.preventDefault(); - - const price = +currPrice * count; - - if (price > MyAsset) { - setLackAssetFlag(true); - timerRef.current = window.setTimeout(() => { - setLackAssetFlag(false); - }, 2000); - return; - } - toggleModal(); - }; - return ( <>
    @@ -130,85 +60,11 @@ export default function TradeSection({ code, data }: TradeSectionProps) {
    {category === 'buy' ? ( -
    -
    -
    -

    매수 가격

    - -
    - {lowerLimitFlag && ( -
    - 이 주식의 최소 가격은 {(+stck_llam).toLocaleString()}입니다. -
    - )} - {upperLimitFlag && ( -
    - 이 주식의 최대 가격은 {(+stck_mxpr).toLocaleString()}입니다. -
    - )} -
    -

    수량

    - setCount(+e.target.value)} - className='flex-1 py-1 rounded-lg' - min={1} - /> -
    -
    - -
    - -
    -
    -

    매수 가능 금액

    -

    0원

    -
    -
    -

    총 주문 금액

    -

    {(+currPrice * count).toLocaleString()}원

    -
    -
    - -
    - {lackAssetFlag && ( -

    잔액이 부족해요!

    - )} -
    - -
    + ) : ( -
    - -

    매도할 주식이 없어요

    -
    + )} - {isOpen && ( - - )} ); } - -function isNumericString(str: string) { - return str.length === 0 || /^[0-9]+$/.test(str); -} diff --git a/FE/src/utils/common.ts b/FE/src/utils/common.ts index 565c43da..6e8408ea 100644 --- a/FE/src/utils/common.ts +++ b/FE/src/utils/common.ts @@ -20,3 +20,7 @@ export function parseTimestamp(timestamp: string) { return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } + +export function isNumericString(str: string) { + return str.length === 0 || /^[0-9]+$/.test(str); +} From c80ab54bba1b931cb40cbfba7796d5df88d7ddc5 Mon Sep 17 00:00:00 2001 From: dongree Date: Thu, 21 Nov 2024 16:25:57 +0900 Subject: [PATCH 73/77] =?UTF-8?q?=E2=9E=95=20add:=20checkAuth=20API=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Header.tsx | 12 ++++++++++++ FE/src/service/auth.ts | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/FE/src/components/Header.tsx b/FE/src/components/Header.tsx index bbc02206..85de5b6e 100644 --- a/FE/src/components/Header.tsx +++ b/FE/src/components/Header.tsx @@ -5,6 +5,8 @@ import useSearchModalStore from '../store/useSearchModalStore.ts'; import useSearchInputStore from '../store/useSearchInputStore.ts'; import logo from 'assets/Logo.png'; import { deleteCookie } from 'utils/common.ts'; +import { checkAuth } from 'service/auth.ts'; +import { useEffect } from 'react'; export default function Header() { const { toggleModal } = useLoginModalStore(); @@ -12,6 +14,16 @@ export default function Header() { const { toggleSearchModal } = useSearchModalStore(); const { searchInput } = useSearchInputStore(); + useEffect(() => { + const check = async () => { + const res = await checkAuth(); + if (res.ok) setIsLogin(true); + else setIsLogin(false); + }; + + check(); + }, [setIsLogin]); + return (
    diff --git a/FE/src/service/auth.ts b/FE/src/service/auth.ts index fd0c555f..566dd5f9 100644 --- a/FE/src/service/auth.ts +++ b/FE/src/service/auth.ts @@ -13,3 +13,14 @@ export async function login( }), }).then((res) => res.json()); } + +export async function checkAuth() { + const url = import.meta.env.PROD + ? `${import.meta.env.VITE_API_URL}/auth/check` + : '/api/auth/check'; + + return fetch(url, { + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + }); +} From 9f20bc66d64f4315ef5a7164b1597ea4090c7058 Mon Sep 17 00:00:00 2001 From: Seo San Date: Thu, 21 Nov 2024 16:48:01 +0900 Subject: [PATCH 74/77] =?UTF-8?q?=E2=9C=A8=20feat:=20dummy=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=95=B4=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=ED=99=95=EC=9D=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Rank/RankCard.tsx | 18 ++-- FE/src/components/Rank/RankList.tsx | 30 ++++--- FE/src/components/Rank/bummyData.ts | 133 ++++++++++++++++++++++++++++ FE/src/page/Rank.tsx | 39 +++----- FE/src/service/getRanking.ts | 7 ++ 5 files changed, 182 insertions(+), 45 deletions(-) create mode 100644 FE/src/components/Rank/bummyData.ts create mode 100644 FE/src/service/getRanking.ts diff --git a/FE/src/components/Rank/RankCard.tsx b/FE/src/components/Rank/RankCard.tsx index b2ac264b..8c25a4f9 100644 --- a/FE/src/components/Rank/RankCard.tsx +++ b/FE/src/components/Rank/RankCard.tsx @@ -1,14 +1,20 @@ -import { TmpDataType } from './RankType.ts'; +import { AssetRankItemType, ProfitRankItemType } from './bummyData.ts'; type Props = { - item: TmpDataType; + item: ProfitRankItemType | AssetRankItemType; ranking: number; type: '수익률순' | '자산순'; }; + export default function RankCard({ item, ranking, type }: Props) { + const isProfitRankItem = ( + item: ProfitRankItemType | AssetRankItemType, + ): item is ProfitRankItemType => { + return 'profitRate' in item; + }; + return (
    @@ -30,11 +36,13 @@ export default function RankCard({ item, ranking, type }: Props) {
    {type === '수익률순' - ? `${item.value}%` + ? isProfitRankItem(item) + ? `${item.profitRate}%` + : '0%' : new Intl.NumberFormat('ko-KR', { notation: 'compact', maximumFractionDigits: 1, - }).format(item.value) + '원'} + }).format((item as AssetRankItemType).totalAsset) + '원'}
    diff --git a/FE/src/components/Rank/RankList.tsx b/FE/src/components/Rank/RankList.tsx index 06ceecb4..3a09fe35 100644 --- a/FE/src/components/Rank/RankList.tsx +++ b/FE/src/components/Rank/RankList.tsx @@ -1,11 +1,15 @@ -import { TmpDataType } from './RankType.ts'; -import RankCard from './RankCard.tsx'; +import RankCard from './RankCard'; +import useAuthStore from '../../store/authStore.ts'; +import { AssetRankingType, ProfitRankingType } from './bummyData.ts'; type Props = { title: '수익률순' | '자산순'; - data: TmpDataType[]; + data: ProfitRankingType | AssetRankingType; }; + export default function RankList({ title, data }: Props) { + const { topRank, userRank } = data; + const { isLogin } = useAuthStore(); return (
    @@ -14,9 +18,9 @@ export default function RankList({ title, data }: Props) {
    - {data.map((item, index) => ( + {topRank.map((item, index) => (
    -
    -
    -

    {`내 ${title} 순위`}

    -
    + {!isLogin && userRank !== null && typeof userRank.rank === 'number' ? ( +
    +
    +

    {`내 ${title} 순위`}

    +
    -
    - +
    + +
    -
    + ) : null}
    ); } diff --git a/FE/src/components/Rank/bummyData.ts b/FE/src/components/Rank/bummyData.ts new file mode 100644 index 00000000..acf27717 --- /dev/null +++ b/FE/src/components/Rank/bummyData.ts @@ -0,0 +1,133 @@ +// 타입 수정 +// 수익률 랭킹 타입 +export type ProfitRankItemType = { + nickname: string; + profitRate: number; + rank?: number; +}; + +// 수익률 랭킹 타입 +export type ProfitRankingType = { + topRank: ProfitRankItemType[]; + userRank: ProfitRankItemType; +}; + +// 자산 랭킹 타입 +export type AssetRankItemType = { + nickname: string; + totalAsset: number; + rank?: number; +}; + +// 자산 랭킹 타입 +export type AssetRankingType = { + topRank: AssetRankItemType[]; + userRank: AssetRankItemType; +}; + +export type RankDataType = { + profitRateRanking: ProfitRankingType; + assetRanking: AssetRankingType; +}; + +// 더미 데이터 +export const dummyRankData: RankDataType = { + profitRateRanking: { + topRank: [ + { + nickname: '투자의신', + profitRate: 356.72, + }, + { + nickname: '주식왕', + profitRate: 245.89, + }, + { + nickname: '워렌버핏', + profitRate: 198.45, + }, + { + nickname: '존버마스터', + profitRate: 156.23, + }, + { + nickname: '주린이탈출', + profitRate: 134.51, + }, + { + nickname: '테슬라홀더', + profitRate: 122.34, + }, + { + nickname: '배당투자자', + profitRate: 98.67, + }, + { + nickname: '단타치는무도가', + profitRate: 87.91, + }, + { + nickname: '가치투자자', + profitRate: 76.45, + }, + { + nickname: '코스피불독', + profitRate: 65.23, + }, + ], + userRank: { + nickname: '나의닉네임', + profitRate: 45.67, + rank: 23, // 23등으로 설정 + }, + }, + assetRanking: { + topRank: [ + { + nickname: '자산왕', + totalAsset: 15800000000, + }, + { + nickname: '억만장자', + totalAsset: 9200000000, + }, + { + nickname: '주식부자', + totalAsset: 7500000000, + }, + { + nickname: '연봉1억', + totalAsset: 6300000000, + }, + { + nickname: '월급쟁이탈출', + totalAsset: 4800000000, + }, + { + nickname: '부자될사람', + totalAsset: 3200000000, + }, + { + nickname: '재테크고수', + totalAsset: 2500000000, + }, + { + nickname: '천만원돌파', + totalAsset: 1800000000, + }, + { + nickname: '주식으로퇴사', + totalAsset: 1200000000, + }, + { + nickname: '투자의시작', + totalAsset: 950000000, + }, + ], + userRank: { + nickname: '나의닉네임', + totalAsset: 850000000, + rank: 15, // 15등으로 설정 + }, + }, +}; diff --git a/FE/src/page/Rank.tsx b/FE/src/page/Rank.tsx index 1ef0604b..6c13c715 100644 --- a/FE/src/page/Rank.tsx +++ b/FE/src/page/Rank.tsx @@ -1,33 +1,16 @@ import Nav from 'components/Rank/Nav.tsx'; import RankList from '../components/Rank/RankList.tsx'; - -const dummyOne = [ - { nickname: 'MasterInvestor', value: 28.5 }, - { nickname: 'StockExpert', value: 22.3 }, - { nickname: 'SuperTrader', value: 19.8 }, - { nickname: 'WallStreetWolf', value: 17.2 }, - { nickname: 'WealthKing', value: 15.9 }, - { nickname: 'LuckyInvestor', value: 14.1 }, - { nickname: 'RichMaker', value: 12.8 }, - { nickname: 'InvestmentGuru', value: 11.5 }, - { nickname: 'MarketAnalyst', value: 10.2 }, - { nickname: 'FutureAsset', value: 9.7 }, -]; - -const dummyTwo = [ - { nickname: 'MasterInvestor', value: 15800000000 }, - { nickname: 'StockExpert', value: 9200000000 }, - { nickname: 'SuperTrader', value: 7500000000 }, - { nickname: 'WallStreetWolf', value: 6300000000 }, - { nickname: 'WealthKing', value: 4800000000 }, - { nickname: 'LuckyInvestor', value: 3200000000 }, - { nickname: 'RichMaker', value: 2500000000 }, - { nickname: 'InvestmentGuru', value: 1800000000 }, - { nickname: 'MarketAnalyst', value: 1200000000 }, - { nickname: 'FutureAsset', value: 950000000 }, -]; +import { dummyRankData } from '../components/Rank/bummyData.ts'; export default function Rank() { + // const { data, isLoading } = useQuery({ + // queryKey: ['Rank'], + // queryFn: () => getRanking(), + // }); + // + // if (isLoading) return
    Loading...
    ; + const data = dummyRankData; + return (
    @@ -35,8 +18,8 @@ export default function Rank() {
    - - + +
    ); diff --git a/FE/src/service/getRanking.ts b/FE/src/service/getRanking.ts new file mode 100644 index 00000000..2c5221d9 --- /dev/null +++ b/FE/src/service/getRanking.ts @@ -0,0 +1,7 @@ +export const getRanking = async () => { + const response = await fetch(`${import.meta.env.VITE_API_URL}/ranking`); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); +}; From c78426b31e966e84c41ae66dbe26e91965e48349 Mon Sep 17 00:00:00 2001 From: dongree Date: Thu, 21 Nov 2024 16:53:55 +0900 Subject: [PATCH 75/77] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore:=20import=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/TradeSection.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index 36bbb19b..71419029 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -1,8 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { StockDetailType } from 'types'; - -import BuySection from './buySection'; import SellSection from './SellSection'; +import BuySection from './buySection'; type TradeSectionProps = { code: string; From e97765f0998fd3647ef419ac4fc7bcabe74867f8 Mon Sep 17 00:00:00 2001 From: dongree Date: Thu, 21 Nov 2024 16:57:09 +0900 Subject: [PATCH 76/77] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20chore:=20import=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/TradeSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FE/src/components/StocksDetail/TradeSection.tsx b/FE/src/components/StocksDetail/TradeSection.tsx index 71419029..d30188c9 100644 --- a/FE/src/components/StocksDetail/TradeSection.tsx +++ b/FE/src/components/StocksDetail/TradeSection.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { StockDetailType } from 'types'; import SellSection from './SellSection'; -import BuySection from './buySection'; +import BuySection from './BuySection'; type TradeSectionProps = { code: string; From 006272f4220603ea099fb9020226113d5a954b60 Mon Sep 17 00:00:00 2001 From: Seo San Date: Thu, 21 Nov 2024 16:57:23 +0900 Subject: [PATCH 77/77] =?UTF-8?q?=F0=9F=94=A7=20fix:=20singlequote?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20lint=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Rank/RankCard.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FE/src/components/Rank/RankCard.tsx b/FE/src/components/Rank/RankCard.tsx index 8c25a4f9..3e74496f 100644 --- a/FE/src/components/Rank/RankCard.tsx +++ b/FE/src/components/Rank/RankCard.tsx @@ -15,7 +15,9 @@ export default function RankCard({ item, ranking, type }: Props) { return (