diff --git a/FE/src/components/Header.tsx b/FE/src/components/Header.tsx index e3a4d6e7..a4d65d5f 100644 --- a/FE/src/components/Header.tsx +++ b/FE/src/components/Header.tsx @@ -1,8 +1,8 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'; -import useAuthStore from 'store/authStore'; +import useAuthStore from 'store/useAuthStore.ts'; import useLoginModalStore from 'store/useLoginModalStore'; -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 logoPng from 'assets/logo.png'; import logoWebp from 'assets/logo.webp'; import { checkAuth, logout } from 'service/auth.ts'; diff --git a/FE/src/components/Login/index.tsx b/FE/src/components/Login/index.tsx index 27b37d56..880d7379 100644 --- a/FE/src/components/Login/index.tsx +++ b/FE/src/components/Login/index.tsx @@ -3,7 +3,7 @@ import Input from './Input'; import { ChatBubbleOvalLeftIcon, XMarkIcon } from '@heroicons/react/16/solid'; import { FormEvent, useEffect, useState } from 'react'; import { login } from 'service/auth'; -import useAuthStore from 'store/authStore'; +import useAuthStore from 'store/useAuthStore.ts'; import Overay from '../ModalOveray.tsx'; export default function Login() { @@ -59,7 +59,7 @@ export default function Login() { toggleModal()} />

JuGa

-

+

{ { '401': '존재하지 않는 사용자입니다.', @@ -67,8 +67,8 @@ export default function Login() { }[errorCode] }

-
-
+ +
- diff --git a/FE/src/components/Login/type.ts b/FE/src/components/Login/type.ts new file mode 100644 index 00000000..06b1b10b --- /dev/null +++ b/FE/src/components/Login/type.ts @@ -0,0 +1,9 @@ +export type LoginSuccessResponse = { + accessToken: string; +}; + +export type LoginFailResponse = { + error: string; + message: string[]; + statusCode: number; +}; diff --git a/FE/src/components/Mypage/CancleAlertModal.tsx b/FE/src/components/Mypage/CancleAlertModal.tsx index 5caeac15..10ba69bc 100644 --- a/FE/src/components/Mypage/CancleAlertModal.tsx +++ b/FE/src/components/Mypage/CancleAlertModal.tsx @@ -1,5 +1,5 @@ import Overay from 'components/ModalOveray'; -import useOrderCancelAlertModalStore from 'store/orderCancleAlertModalStore'; +import useOrderCancelAlertModalStore from 'store/useOrderCancleAlertModalStore'; export default function CancleAlertModal() { const { close, onSuccess, order } = useOrderCancelAlertModalStore(); @@ -17,7 +17,7 @@ export default function CancleAlertModal() {
-
-

+

+

{data.description}

diff --git a/FE/src/components/News/News.tsx b/FE/src/components/News/News.tsx index 12696f9e..aaf48a77 100644 --- a/FE/src/components/News/News.tsx +++ b/FE/src/components/News/News.tsx @@ -1,7 +1,7 @@ import Card from './Card.tsx'; import { useQuery } from '@tanstack/react-query'; -import { getNewsData } from '../../service/getNewsData.ts'; -import { NewsDataType } from './NewsDataType.ts'; +import { getNewsData } from '../../service/news.ts'; +import { NewsDataType } from './type.ts'; export default function News() { const { data, isLoading, isError } = useQuery({ @@ -17,7 +17,7 @@ export default function News() { return (
-
+

주요 뉴스

diff --git a/FE/src/components/News/newsMockData.ts b/FE/src/components/News/newsMockData.ts deleted file mode 100644 index cb3020a5..00000000 --- a/FE/src/components/News/newsMockData.ts +++ /dev/null @@ -1,46 +0,0 @@ -export type NewsMockDataType = { - publisher: string; - img: string; - title: string; - date: string; - link: string; -}; - -export const newsMockData: NewsMockDataType[] = [ - { - publisher: '중앙일보', - img: 'https://s.pstatic.net/dthumb.phinf/?src=%22https%3A%2F%2Fs.pstatic.net%2Fstatic%2Fnewsstand%2F2024%2F1125%2Farticle_img%2Fnew_main%2F9152%2F071102_001.jpg%22&type=nf312_208&service=navermain', - title: '[단독] 반도체 산업 신규 투자 확대...정부 지원책 발표', - date: '11월 03일 18:55 직접 편집', - link: 'https://www.joongang.co.kr/article/25288962', - }, - { - publisher: '동아일보', - img: 'https://s.pstatic.net/dthumb.phinf/?src=%22https%3A%2F%2Fs.pstatic.net%2Fstatic%2Fnewsstand%2F2024%2F1125%2Farticle_img%2Fnew_main%2F9131%2F172557_001.jpg%22&type=nf312_208&service=navermain', - title: - '청년 주거대책 발표...월세 지원 확대https://s.pstatic.net/dthumb.phinf/?src=%22https%3A%2F%2Fs.pstatic.net%2Fstatic%2Fnewsstand%2F2024%2F1125%2Farticle_img%2Fnew_main%2F9131%2F172557_001.jpg%22&type=nf312_208&service=navermain', - date: '11월 03일 18:39 직접 편집', - link: 'https://www.donga.com/news/article/all/20241103/123456', - }, - { - publisher: '한국경제', - img: 'https://s.pstatic.net/dthumb.phinf/?src=%22https%3A%2F%2Fs.pstatic.net%2Fstatic%2Fnewsstand%2F2024%2F1125%2Farticle_img%2Fnew_main%2F9029%2F175118_001.jpg%22&type=nf312_208&service=navermain', - title: '기준금리 동결 전망...시장 영향은?', - date: '11월 03일 17:50 직접 편집', - link: 'https://www.hankyung.com/article/2024110387654', - }, - { - publisher: '매일경제', - img: 'https://s.pstatic.net/dthumb.phinf/?src=%22https%3A%2F%2Fs.pstatic.net%2Fstatic%2Fnewsstand%2F2024%2F1125%2Farticle_img%2Fnew_main%2F9243%2F174551_001.jpg%22&type=nf312_208&service=navermain', - title: '글로벌 기업들의 韓 투자 러시...배경은?', - date: '11월 03일 17:30 직접 편집', - link: 'https://www.mk.co.kr/news/2024/11/123987', - }, - { - publisher: '한겨레', - img: 'https://s.pstatic.net/static/newsstand/2024/1103/article_img/9005/171525_001.jpg', - title: '환경부, 신재생에너지 정책 전면 개편 추진', - date: '11월 03일 17:15 직접 편집', - link: 'https://www.hani.co.kr/arti/20241103-654321', - }, -]; diff --git a/FE/src/components/News/NewsDataType.ts b/FE/src/components/News/type.ts similarity index 100% rename from FE/src/components/News/NewsDataType.ts rename to FE/src/components/News/type.ts diff --git a/FE/src/components/Rank/RankCard.tsx b/FE/src/components/Rank/Card.tsx similarity index 91% rename from FE/src/components/Rank/RankCard.tsx rename to FE/src/components/Rank/Card.tsx index fef5a8e8..4e56cc82 100644 --- a/FE/src/components/Rank/RankCard.tsx +++ b/FE/src/components/Rank/Card.tsx @@ -1,11 +1,11 @@ -import { RankingItem } from './RankType.ts'; +import { RankingItem } from './type.ts'; type Props = { item: RankingItem; type: '수익률순' | '자산순'; }; -export default function RankCard({ item, type }: Props) { +export default function Card({ item, type }: Props) { return (
-
-
+
+

{title}

{topRank.map((item, index) => ( - + ))}
@@ -34,7 +30,7 @@ export default function RankList({ title, data }: Props) {
- +
) : null} diff --git a/FE/src/components/Rank/RankType.ts b/FE/src/components/Rank/type.ts similarity index 100% rename from FE/src/components/Rank/RankType.ts rename to FE/src/components/Rank/type.ts diff --git a/FE/src/components/Search/SearchCard.tsx b/FE/src/components/Search/SearchCard.tsx index 860f09f4..24633522 100644 --- a/FE/src/components/Search/SearchCard.tsx +++ b/FE/src/components/Search/SearchCard.tsx @@ -1,8 +1,8 @@ -import { SearchDataType } from './searchDataType.ts'; import { useNavigate } from 'react-router-dom'; import useSearchModalStore from 'store/useSearchModalStore.ts'; import useSearchInputStore from 'store/useSearchInputStore.ts'; import { SearchCardHighLight } from './SearchCardHighlight.tsx'; +import { SearchDataType } from './type.ts'; type SearchCardProps = { data: SearchDataType; diff --git a/FE/src/components/Search/SearchCardHighlight.tsx b/FE/src/components/Search/SearchCardHighlight.tsx index 8c543443..d5e1306c 100644 --- a/FE/src/components/Search/SearchCardHighlight.tsx +++ b/FE/src/components/Search/SearchCardHighlight.tsx @@ -1,4 +1,4 @@ -import { formatNoSpecialChar } from '../../utils/formatNoSpecialChar.ts'; +import { formatNoSpecialChar } from 'utils/format.ts'; type SearchCardHighLightProps = { text: string; diff --git a/FE/src/components/Search/SearchHistoryList.tsx b/FE/src/components/Search/SearchHistoryList.tsx index caa1d7df..3a64ca75 100644 --- a/FE/src/components/Search/SearchHistoryList.tsx +++ b/FE/src/components/Search/SearchHistoryList.tsx @@ -1,5 +1,5 @@ import { SearchHistoryItem } from './SearchHistoryItem.tsx'; -import { HistoryType } from './searchDataType.ts'; +import { HistoryType } from './type.ts'; type SearchHistoryListProps = { searchHistory: HistoryType[]; diff --git a/FE/src/components/Search/SearchList.tsx b/FE/src/components/Search/SearchList.tsx index 89c0adf6..f3e06475 100644 --- a/FE/src/components/Search/SearchList.tsx +++ b/FE/src/components/Search/SearchList.tsx @@ -1,7 +1,7 @@ import SearchCard from './SearchCard.tsx'; -import { SearchDataType } from './searchDataType.ts'; import Lottie from 'lottie-react'; import noResultAnimation from 'assets/noResultAnimation.json'; +import { SearchDataType } from './type.ts'; type SearchListProps = { searchData: SearchDataType[]; diff --git a/FE/src/components/Search/index.tsx b/FE/src/components/Search/index.tsx index e9a543c4..9b4ef2fb 100644 --- a/FE/src/components/Search/index.tsx +++ b/FE/src/components/Search/index.tsx @@ -5,13 +5,13 @@ import { SearchInput } from './SearchInput'; import { SearchHistoryList } from './SearchHistoryList'; import SearchList from './SearchList.tsx'; import useSearchInputStore from 'store/useSearchInputStore.ts'; -import { useDebounce } from 'utils/useDebounce.ts'; +import { useDebounce } from 'hooks/useDebounce.ts'; import { useQuery } from '@tanstack/react-query'; -import { getSearchResults } from 'service/getSearchResults.ts'; import Lottie from 'lottie-react'; import searchAnimation from 'assets/searchAnimation.json'; -import { useSearchHistory } from './searchHistoryHook.ts'; -import { formatNoSpecialChar } from '../../utils/formatNoSpecialChar.ts'; +import { useSearchHistory } from 'hooks/useSearchHistoryHook.ts'; +import { getSearchResults } from 'service/search.ts'; +import { formatNoSpecialChar } from 'utils/format.ts'; export default function SearchModal() { const { isOpen, toggleSearchModal } = useSearchModalStore(); @@ -36,7 +36,7 @@ export default function SearchModal() { if (data && data.length > 0 && debounceValue && !isLoading) { addSearchHistory(formatNoSpecialChar(debounceValue)); } - }, [data, debounceValue]); + }, [data, debounceValue, addSearchHistory, isLoading]); if (!isOpen) return null; diff --git a/FE/src/components/Search/searchDataType.ts b/FE/src/components/Search/type.ts similarity index 100% rename from FE/src/components/Search/searchDataType.ts rename to FE/src/components/Search/type.ts diff --git a/FE/src/components/StockIndex/Card.tsx b/FE/src/components/StockIndex/Card.tsx index bd1e5dce..9b49511d 100644 --- a/FE/src/components/StockIndex/Card.tsx +++ b/FE/src/components/StockIndex/Card.tsx @@ -1,17 +1,16 @@ +import { useEffect, useRef, useState } from 'react'; +import { socket } from 'utils/socket.ts'; +import { drawChart } from 'utils/chart/drawChart.ts'; import { ChartData, + MarketType, StockIndexData, StockIndexValue, -} from 'components/TopFive/type'; -import { useEffect, useRef, useState } from 'react'; -import { socket } from 'utils/socket.ts'; -import { drawChart } from 'utils/chart/drawChart.ts'; - -// const X_LENGTH = 79; +} from './type.ts'; type StockIndexChartProps = { name: string; - id: 'KOSPI' | 'KOSDAQ' | 'KOSPI200' | 'KSQ150'; + id: MarketType; initialData: StockIndexData; }; diff --git a/FE/src/components/StockIndex/index.tsx b/FE/src/components/StockIndex/index.tsx index 6b0adb1e..3523d417 100644 --- a/FE/src/components/StockIndex/index.tsx +++ b/FE/src/components/StockIndex/index.tsx @@ -1,6 +1,6 @@ +import { getStockIndex } from 'service/stocks.ts'; import { Card } from './Card.tsx'; import { useQuery } from '@tanstack/react-query'; -import { getStockIndex } from '../../service/getStockIndex.ts'; export default function StockIndex() { const { data, isLoading, isError } = useQuery({ @@ -16,7 +16,7 @@ export default function StockIndex() { const { KOSPI, KOSDAQ, KOSPI200, KSQ150 } = data; return ( -
+
diff --git a/FE/src/components/StockIndex/type.ts b/FE/src/components/StockIndex/type.ts new file mode 100644 index 00000000..77a10c4c --- /dev/null +++ b/FE/src/components/StockIndex/type.ts @@ -0,0 +1,15 @@ +export type ChartData = { time: string; value: string; diff: string }; + +export type StockIndexValue = { + curr_value: string; + diff: string; + diff_rate: string; + sign: string; +}; + +export type StockIndexData = { + chart: ChartData[]; + value: StockIndexValue; +}; + +export type MarketType = 'KOSPI' | 'KOSDAQ' | 'KOSPI200' | 'KSQ150'; diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index ca702a04..445bfa2d 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -1,364 +1,57 @@ -import { - MouseEvent, - useCallback, - useEffect, - useRef, - useState, - WheelEvent, -} from 'react'; -import { - ChartSizeConfigType, - Padding, - StockChartUnit, - TiemCategory, -} from 'types'; +import { useEffect, useRef, useState } from 'react'; +import { TiemCategory } 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 { drawChartGrid } from 'utils/chart/drawChartGrid.ts'; -import { drawMouseGrid } from 'utils/chart/drawMouseGrid.ts'; import { EyeIcon, EyeSlashIcon } from '@heroicons/react/16/solid'; - -const categories: { label: string; value: TiemCategory }[] = [ - { label: '일', value: 'D' }, - { label: '주', value: 'W' }, - { label: '월', value: 'M' }, - { label: '년', value: 'Y' }, -]; - -const padding: Padding = { - top: 20, - right: 80, - bottom: 10, - left: 40, -}; - -type StocksDeatailChartProps = { +import { setCanvasSize } from 'utils/chart/setCanvasSize.ts'; +import { useMouseMove } from 'hooks/useMouseMove.ts'; +import { useMouseUpDown } from 'hooks/useMouseUpDown.ts'; +import { useMouseWheel } from 'hooks/useMouseWheel.ts'; +import { categories } from 'constants.ts'; +import { useCanvasRef } from 'hooks/useCanvasRef.ts'; +import { useCanvasResize } from 'hooks/useCanvasResize.ts'; +import { renderChart } from 'utils/renderChart.ts'; + +type StocksDetailChartProps = { code: string; }; -export type MousePositionType = { - x: number; - y: number; -}; - -export default function Chart({ code }: StocksDeatailChartProps) { +export default function Chart({ code }: StocksDetailChartProps) { const containerRef = useRef(null); - const upperChartCanvasRef = useRef(null); - const lowerChartCanvasRef = useRef(null); - const upperChartY = useRef(null); - const lowerChartY = useRef(null); - const chartX = useRef(null); - const rafRef = useRef(); + const { + upperChartCanvasRef, + lowerChartCanvasRef, + upperChartY, + lowerChartY, + chartX, + } = useCanvasRef(); const [timeCategory, setTimeCategory] = useState('D'); const [moveAverageToggle, setMoveAverageToggle] = useState(true); - const [charSizeConfig, setChartSizeConfig] = useState({ - upperHeight: 0.5, - lowerHeight: 0.4, - chartWidth: 0.92, - yAxisWidth: 0.08, - xAxisHeight: 0.1, - }); - const [isDragging, setIsDragging] = useState(false); - const [upperLabelNum, setUpperLabelNum] = useState(3); - const [lowerLabelNum, setLowerLabelNum] = useState(3); - const [mousePosition, setMousePosition] = useState({ - x: 0, - y: 0, - }); + const [mouseIndex, setMouseIndex] = useState(null); - const [dataRange, setDataRange] = useState({ - start: 0, - end: 0, - }); - const minDisplayData = 20; const { data, isLoading } = useQuery( ['stocksChartData', code, timeCategory], () => getStocksChartDataByCode(code, timeCategory), - { staleTime: 1000 }, - ); - - const handleMouseDown = useCallback((e: MouseEvent) => { - e.preventDefault(); - setIsDragging(true); - }, []); - - const handleMouseMove = useCallback( - (e: globalThis.MouseEvent) => { - if (!isDragging || !containerRef.current) return; - const minHeight = 0.2; - const containerRect = containerRef.current.getBoundingClientRect(); - const mouseY = e.clientY - containerRect.top; - const 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)); - } - }, - [ - isDragging, - containerRef, - setChartSizeConfig, - setUpperLabelNum, - setLowerLabelNum, - ], - ); - - const handleMouseUp = useCallback(() => { - setIsDragging(false); - }, []); - - const getCanvasMousePosition = (e: MouseEvent) => { - if (!containerRef.current) return; - - if (rafRef.current) { - cancelAnimationFrame(rafRef.current); - } - - rafRef.current = requestAnimationFrame(() => { - const rect = containerRef.current?.getBoundingClientRect(); - if (!rect) return; - - setMousePosition({ - x: (e.clientX - rect.left) * 2, - y: (e.clientY - rect.top) * 2, - }); - }); - }; - - useEffect(() => { - if (isDragging) { - window.addEventListener('mousemove', handleMouseMove); - window.addEventListener('mouseup', handleMouseUp); - } - - return () => { - window.removeEventListener('mousemove', handleMouseMove); - window.removeEventListener('mouseup', handleMouseUp); - if (rafRef.current) { - cancelAnimationFrame(rafRef.current); - } - }; - }, [isDragging, handleMouseDown, handleMouseUp, handleMouseMove]); - const setCanvasSize = useCallback( - (canvas: HTMLCanvasElement, widthConfig: number, heightConfig: number) => { - if (!containerRef.current) return; - - canvas.width = containerRef.current.clientWidth * widthConfig * 2; - canvas.height = containerRef.current.clientHeight * heightConfig * 2; - canvas.style.width = `${containerRef.current.clientWidth * widthConfig}px`; - canvas.style.height = `${containerRef.current.clientHeight * heightConfig}px`; - }, - [containerRef], + { staleTime: 1000 * 60 }, ); - const renderChart = useCallback( - ( - upperChartCanvas: HTMLCanvasElement, - lowerChartCanvas: HTMLCanvasElement, - upperChartYCanvas: HTMLCanvasElement, - lowerChartYCanvas: HTMLCanvasElement, - chartXCanvas: HTMLCanvasElement, - chartData: StockChartUnit[], - mousePosition: MousePositionType, - ) => { - const UpperChartCtx = upperChartCanvas.getContext('2d'); - const LowerChartCtx = lowerChartCanvas.getContext('2d'); - const UpperYCtx = upperChartYCanvas.getContext('2d'); - const LowerYCtx = lowerChartYCanvas.getContext('2d'); - const ChartXCtx = chartXCanvas.getContext('2d'); - const displayData = chartData.slice(dataRange.start, dataRange.end + 1); - if ( - !UpperChartCtx || - !LowerChartCtx || - !UpperYCtx || - !LowerYCtx || - !ChartXCtx - ) - return; - - drawChartGrid( - UpperChartCtx, - upperChartCanvas.width - padding.left - padding.right, - upperChartCanvas.height - padding.top - padding.bottom, - upperLabelNum, - LowerChartCtx, - lowerChartCanvas.width - padding.left - padding.right, - lowerChartCanvas.height - padding.top - padding.bottom, - lowerLabelNum, - displayData, - padding, - ); - - if (moveAverageToggle) { - drawLineChart( - UpperChartCtx, - displayData, - 0, - 0, - upperChartCanvas.width - padding.left - padding.right, - upperChartCanvas.height - padding.top - padding.bottom, - padding, - 0.1, - ); - } - - drawCandleChart( - UpperChartCtx, - displayData, - 0, - 0, - upperChartCanvas.width - padding.left - padding.right, - upperChartCanvas.height - padding.top - padding.bottom, - padding, - 0.1, - ); - - drawBarChart( - LowerChartCtx, - displayData, - lowerChartCanvas.width - padding.left - padding.right, - lowerChartCanvas.height - padding.top - padding.bottom, - padding, - ); - - drawUpperYAxis( - UpperYCtx, - displayData, - upperChartYCanvas.width - padding.left - padding.right, - upperChartYCanvas.height - padding.top - padding.bottom, - upperLabelNum, - padding, - 0.1, - mousePosition, - upperChartCanvas.width, - upperChartCanvas.height, - ); - - drawLowerYAxis( - LowerYCtx, - displayData, - lowerChartYCanvas.width - padding.left - padding.right, - lowerChartYCanvas.height - padding.top - padding.bottom, - lowerLabelNum, - padding, - mousePosition, - lowerChartCanvas.width, - lowerChartCanvas.height, - upperChartCanvas.height, - ); - - drawXAxis( - ChartXCtx, - displayData, - chartXCanvas.width - padding.left - padding.right, - chartXCanvas.height, - padding, - mousePosition, - upperChartCanvas.height + lowerChartCanvas.height, - setMouseIndex, - ); - - 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, - ); - } + const { mousePosition, getCanvasMousePosition } = useMouseMove(containerRef); + const { chartSizeConfig, upperLabelNum, lowerLabelNum, handleMouseDown } = + useMouseUpDown(containerRef); + const { dataRange, handleWheel } = useMouseWheel(data); + const { resizeCanvases } = useCanvasResize( + { + upperChartCanvasRef, + lowerChartCanvasRef, + upperChartY, + lowerChartY, + chartX, }, - [ - padding, - upperLabelNum, - lowerLabelNum, - drawChartGrid, - drawLineChart, - drawCandleChart, - drawBarChart, - drawUpperYAxis, - drawLowerYAxis, - drawXAxis, - moveAverageToggle, - dataRange, - ], - ); - const handleWheel = useCallback( - (e: WheelEvent) => { - if (!data) return; - - const wheelPower = 0.1; - const curRange = dataRange.end - dataRange.start; - - // 축소 - if (e.deltaY > 0) { - // 늘어나야함 & 데이터 최대 갯수보다 작아야함. - const newRange = Math.min(curRange * (1 + wheelPower), data.length); - const newStart = Math.max(data.length - newRange, 0); - - setDataRange({ - start: newStart, - end: data.length - 1, - }); - } - - // 확대 - if (e.deltaY < 0) { - // 줄어야함 & 최소 데이터 갯수보단 커야함. - const newRange = Math.max(curRange * (1 - wheelPower), minDisplayData); - const newStart = Math.max(data.length - newRange, 0); - - setDataRange({ - start: newStart, - end: data.length - 1, - }); - } - }, - [data, dataRange], + chartSizeConfig, + containerRef, ); - useEffect(() => { - if (data) { - setDataRange((prev) => ({ - ...prev, - start: 0, - end: data.length - 1, - })); - } - }, [data]); - useEffect(() => { if (isLoading || !data) return; @@ -370,35 +63,8 @@ export default function Chart({ code }: StocksDeatailChartProps) { !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, - ); + resizeCanvases(); renderChart( upperChartCanvasRef.current, @@ -408,6 +74,11 @@ export default function Chart({ code }: StocksDeatailChartProps) { chartX.current, data, mousePosition, + dataRange, + upperLabelNum, + lowerLabelNum, + moveAverageToggle, + setMouseIndex, ); }, [ timeCategory, @@ -415,9 +86,15 @@ export default function Chart({ code }: StocksDeatailChartProps) { isLoading, setCanvasSize, renderChart, - charSizeConfig, + chartSizeConfig, mousePosition, + dataRange, + upperLabelNum, + lowerLabelNum, + moveAverageToggle, + setMouseIndex, ]); + return (
@@ -445,24 +122,6 @@ export default function Chart({ code }: StocksDeatailChartProps) {
- {mouseIndex !== null && data ? ( -
- - 시작 {Number(data[mouseIndex].stck_oprc).toLocaleString()}원 - - - 고가 {Number(data[mouseIndex].stck_hgpr).toLocaleString()}원 - - - 저가 {Number(data[mouseIndex].stck_lwpr).toLocaleString()}원 - - - 종가 {Number(data[mouseIndex].stck_clpr).toLocaleString()}원 - -
- ) : null}
+ {mouseIndex !== null && data ? ( +
+ + 시작 {Number(data[mouseIndex].stck_oprc).toLocaleString()}원 + + + 고가 {Number(data[mouseIndex].stck_hgpr).toLocaleString()}원 + + + 저가 {Number(data[mouseIndex].stck_lwpr).toLocaleString()}원 + + + 종가 {Number(data[mouseIndex].stck_clpr).toLocaleString()}원 + +
+ ) : null}
diff --git a/FE/src/components/StocksDetail/Header.tsx b/FE/src/components/StocksDetail/Header.tsx index e7a54720..122c8470 100644 --- a/FE/src/components/StocksDetail/Header.tsx +++ b/FE/src/components/StocksDetail/Header.tsx @@ -3,7 +3,7 @@ import Toast from 'components/Toast'; import { useEffect, useState } from 'react'; import { bookmark, unbookmark } from 'service/bookmark'; import { unsubscribe } from 'service/stocks'; -import useAuthStore from 'store/authStore'; +import useAuthStore from 'store/useAuthStore.ts'; import useLoginModalStore from 'store/useLoginModalStore'; import { StockDetailType } from 'types'; import { stringToLocaleString } from 'utils/common'; @@ -95,7 +95,7 @@ export default function Header({ code, data }: StocksDetailHeaderProps) { currPrdyVrssSign === '3' ? '' : currPrdyVrssSign < '3' ? '+' : '-'; return ( -
+

{hts_kor_isnm}

diff --git a/FE/src/components/StocksDetail/PriceTableColumn.tsx b/FE/src/components/StocksDetail/PriceSection/TableColumn.tsx similarity index 90% rename from FE/src/components/StocksDetail/PriceTableColumn.tsx rename to FE/src/components/StocksDetail/PriceSection/TableColumn.tsx index 8c548ba4..92642593 100644 --- a/FE/src/components/StocksDetail/PriceTableColumn.tsx +++ b/FE/src/components/StocksDetail/PriceSection/TableColumn.tsx @@ -1,7 +1,10 @@ +import { PriceSectionViewType } from 'types'; + type Props = { - viewMode: boolean; + viewMode: PriceSectionViewType; }; -export default function PriceTableColumn({ viewMode }: Props) { + +export default function TableColumn({ viewMode }: Props) { if (!viewMode) { return ( diff --git a/FE/src/components/StocksDetail/PriceTableDayCard.tsx b/FE/src/components/StocksDetail/PriceSection/TableDayCard.tsx similarity index 86% rename from FE/src/components/StocksDetail/PriceTableDayCard.tsx rename to FE/src/components/StocksDetail/PriceSection/TableDayCard.tsx index 62bde95a..7b9af93c 100644 --- a/FE/src/components/StocksDetail/PriceTableDayCard.tsx +++ b/FE/src/components/StocksDetail/PriceSection/TableDayCard.tsx @@ -1,11 +1,11 @@ -import { DailyPriceDataType } from './PriceDataType.ts'; -import { formatTime } from '../../utils/formatTime.ts'; +import { formatTime } from 'utils/format.ts'; +import { DailyPriceDataType } from './type.ts'; type PriceTableDayCardProps = { data: DailyPriceDataType; }; -export default function PriceTableDayCard({ data }: PriceTableDayCardProps) { +export default function TableDayCard({ data }: PriceTableDayCardProps) { const color = data.prdy_vrss_sign === '3' ? '' diff --git a/FE/src/components/StocksDetail/PriceTableLiveCard.tsx b/FE/src/components/StocksDetail/PriceSection/TableLiveCard.tsx similarity index 89% rename from FE/src/components/StocksDetail/PriceTableLiveCard.tsx rename to FE/src/components/StocksDetail/PriceSection/TableLiveCard.tsx index 9ccb6a2c..b1e44d0c 100644 --- a/FE/src/components/StocksDetail/PriceTableLiveCard.tsx +++ b/FE/src/components/StocksDetail/PriceSection/TableLiveCard.tsx @@ -1,9 +1,9 @@ -import { PriceDataType } from './PriceDataType.ts'; +import { PriceDataType } from './type.ts'; type PriceTableLiveCardProps = { data: PriceDataType; }; -export default function PriceTableLiveCard({ data }: PriceTableLiveCardProps) { +export default function TableLiveCard({ data }: PriceTableLiveCardProps) { const color = data.prdy_vrss_sign === '3' ? '' diff --git a/FE/src/components/StocksDetail/PriceSection.tsx b/FE/src/components/StocksDetail/PriceSection/index.tsx similarity index 76% rename from FE/src/components/StocksDetail/PriceSection.tsx rename to FE/src/components/StocksDetail/PriceSection/index.tsx index 3bbb13c4..6f11d0f8 100644 --- a/FE/src/components/StocksDetail/PriceSection.tsx +++ b/FE/src/components/StocksDetail/PriceSection/index.tsx @@ -1,41 +1,41 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import PriceTableColumn from './PriceTableColumn.tsx'; -import PriceTableLiveCard from './PriceTableLiveCard.tsx'; -import PriceTableDayCard from './PriceTableDayCard.tsx'; +import TableColumn from './TableColumn.tsx'; +import TableLiveCard from './TableLiveCard.tsx'; +import TableDayCard from './TableDayCard.tsx'; import { useParams } from 'react-router-dom'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { DailyPriceDataType, PriceDataType } from './PriceDataType.ts'; -import { getTradeHistory } from 'service/getTradeHistory.ts'; +import { getTradeHistory } from 'service/tradeHistory.ts'; import { socket } from 'utils/socket.ts'; +import { DailyPriceDataType, PriceDataType } from './type.ts'; +import { PriceSectionViewType } from 'types.ts'; export default function PriceSection() { const { id } = useParams(); - const [buttonFlag, setButtonFlag] = useState(true); + const [viewMode, setViewMode] = useState('today'); const indicatorRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); const queryClient = useQueryClient(); const { data: tradeData = [], isLoading } = useQuery({ - queryKey: ['detail', id, buttonFlag], - queryFn: () => getTradeHistory(id as string, buttonFlag), - cacheTime: 30000, - staleTime: 1000, + queryKey: ['detail', id, viewMode], + queryFn: () => getTradeHistory(id as string, viewMode), + staleTime: 1000 * 60 * 3, }); const addData = useCallback( (newData: PriceDataType) => { queryClient.setQueryData( - ['detail', id, buttonFlag], + ['detail', id, viewMode], (old: PriceDataType[] = []) => { return [newData, ...old].slice(0, 30); }, ); }, - [id, buttonFlag], + [id, viewMode, queryClient], ); useEffect(() => { - if (!buttonFlag) return; + if (viewMode === 'daily') return; const handleTradeHistory = (chartData: PriceDataType) => { addData(chartData); }; @@ -44,10 +44,10 @@ export default function PriceSection() { return () => { socket.off(`trade-history/${id}`, handleTradeHistory); }; - }, [id, addData, buttonFlag]); + }, [id, addData, viewMode]); useEffect(() => { - const tmpIndex = buttonFlag ? 0 : 1; + const tmpIndex = viewMode === 'today' ? 0 : 1; const currentButton = buttonRefs.current[tmpIndex]; const indicator = indicatorRef.current; @@ -55,7 +55,7 @@ export default function PriceSection() { indicator.style.left = `${currentButton.offsetLeft}px`; indicator.style.width = `${currentButton.offsetWidth}px`; } - }, [buttonFlag]); + }, [viewMode]); return (