diff --git a/src/_MarketDetailPage/components/ChartFilterBar.tsx b/src/_MarketDetailPage/components/ChartFilterBar.tsx index d1e1f88..c23f727 100644 --- a/src/_MarketDetailPage/components/ChartFilterBar.tsx +++ b/src/_MarketDetailPage/components/ChartFilterBar.tsx @@ -1,15 +1,23 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ChartCandlestick, ChartLine } from "lucide-react"; +import { type Period, type ChartType } from "@/_MarketDetailPage/types/stockDataType"; interface ChartFilterBarProps { + period: Period; + chartType: ChartType; onChangePeriod: (value: string) => void; onChangeChartType: (value: string) => void; } -const ChartFilterBar = ({ onChangePeriod, onChangeChartType }: ChartFilterBarProps) => { +const ChartFilterBar = ({ + period, + chartType, + onChangePeriod, + onChangeChartType, +}: ChartFilterBarProps) => { return (
- + - + { - const [period, setPeriod] = useState("1M"); - const [chartType, setChartType] = useState("candlestick"); +const StockChart = ({ stockCode, period, chartType }: StockChartProps) => { const isFirstRender = useRef(true); - const handleChangePeriod = (value: string) => { - setPeriod(value as Period); - isFirstRender.current = false; - }; + // period에 해당하는 API 요청 + const { data: stockData, isLoading } = useGetStockDetail(stockCode, period); - const handleChangeChartType = (value: string) => { - setChartType(value as ChartType); + useEffect(() => { isFirstRender.current = false; - }; + }, [chartType, period]); const formatDate = (dateString: string) => { - const year = dateString.substring(0, 4); - const month = dateString.substring(4, 6); - const day = dateString.substring(6, 8); + const parts = dateString.split("-"); + const year = parts[0]; + const month = parts[1]; + const day = parts[2]; if (period === "10Y") { return `${year}.${month}`; @@ -38,15 +33,34 @@ const StockChart = ({ stockData }: StockChartProps) => { } }; - const dates = stockData.map((item) => formatDate(item.baseDate)); - const candleData = stockData.map((item) => [ + // period에 따라 데이터 필터링 (useMemo는 early return 전에 호출) + const filteredDataByPeriod = useMemo(() => { + if (!stockData || !stockData.stockPriceList) { + return []; + } + + // 모든 데이터를 역순으로 반환 (10년 데이터도 모두 렌더링) + return [...stockData.stockPriceList].reverse(); + }, [stockData, period]); + + // API 데이터가 없으면 로딩 표시 + if (isLoading || !stockData || !stockData.stockPriceList || filteredDataByPeriod.length === 0) { + return ( +
+ +
+ ); + } + + const dates = filteredDataByPeriod.map((item) => formatDate(item.baseDate)); + const candleData = filteredDataByPeriod.map((item) => [ item.openPrice, item.closePrice, item.lowPrice, item.highPrice, ]); - const lows = stockData.map((d) => d.lowPrice); - const highs = stockData.map((d) => d.highPrice); + const lows = filteredDataByPeriod.map((d) => d.lowPrice); + const highs = filteredDataByPeriod.map((d) => d.highPrice); const option = { animation: isFirstRender.current, @@ -99,7 +113,10 @@ const StockChart = ({ stockData }: StockChartProps) => { }, series: { type: chartType === "candlestick" ? "candlestick" : "line", - data: chartType === "candlestick" ? candleData : stockData.map((item) => item.closePrice), + data: + chartType === "candlestick" + ? candleData + : filteredDataByPeriod.map((item) => item.closePrice), smooth: false, itemStyle: chartType === "candlestick" @@ -138,10 +155,6 @@ const StockChart = ({ stockData }: StockChartProps) => { }; return (
-
); diff --git a/src/_MarketDetailPage/datas/stockSample.ts b/src/_MarketDetailPage/datas/stockSample.ts index a26bd08..ca60ae0 100644 --- a/src/_MarketDetailPage/datas/stockSample.ts +++ b/src/_MarketDetailPage/datas/stockSample.ts @@ -9,7 +9,7 @@ export const sampleData: StockData = { listedShares: 5969782550, marketCap: 437200000000000, currentPrice: 109800, - priceHistory: [ + stockPriceList: [ { baseDate: "20251021", openPrice: 108000, diff --git a/src/_MarketDetailPage/types/stockDataType.ts b/src/_MarketDetailPage/types/stockDataType.ts index 503d9ee..19a1e2f 100644 --- a/src/_MarketDetailPage/types/stockDataType.ts +++ b/src/_MarketDetailPage/types/stockDataType.ts @@ -1,4 +1,4 @@ -export interface PriceHistory { +export interface StockPriceList { baseDate: string; openPrice: number; closePrice: number; @@ -17,7 +17,7 @@ export interface StockData { listedShares: number; marketCap: number; currentPrice: number; - priceHistory: PriceHistory[]; + stockPriceList: StockPriceList[]; } export type Period = "1W" | "1M" | "1Y" | "10Y"; diff --git a/src/_MarketsPage/components/MarketList.tsx b/src/_MarketsPage/components/MarketList.tsx index d061f2e..cfe670b 100644 --- a/src/_MarketsPage/components/MarketList.tsx +++ b/src/_MarketsPage/components/MarketList.tsx @@ -1,8 +1,9 @@ -import type { MarketItem } from "@/_MarketsPage/types/marketItem"; +import type { StockListItem } from "@/_MarketsPage/types/marketItem"; import { useNavigate } from "react-router-dom"; +import { formatNumber } from "@/lib/utils"; interface MarketListProps { - items: MarketItem[]; + items: StockListItem[]; currentPage: number; itemsPerPage: number; } @@ -24,39 +25,41 @@ const MarketList = ({ items, currentPage, itemsPerPage }: MarketListProps) => { 자산명 현재가 등락률 - 거래대금 + 시가총액 {items.map((item, index) => { - const { className, icon } = getChangeInfo(item.changeRate); + const latestPrice = item.stockPriceList?.[0]; + const changeRate = latestPrice?.changeRate ?? 0; + const { className, icon } = getChangeInfo(changeRate); return ( navigate(`/markets/${item.code}`)} + onClick={() => navigate(`/markets/${item.stockCode}`)} > {/* 순번 */} {startIndex + index + 1} {/* 종목명과 코드 */} -
{item.name}
-
{item.code}
+
{item.stockName}
+
{item.stockCode}
{/* 현재가 */} - {item.price.toLocaleString()}원 + {formatNumber(latestPrice?.closePrice ?? 0)}원 {/* 등락률 */} - {icon} {Math.abs(item.changeRate).toFixed(2)}% + {icon} {Math.abs(changeRate).toFixed(2)}% - {/* 거래대금 */} - {item.tradeVolume} + {/* 시가총액 */} + {formatNumber(item.marketCap)} ); })} diff --git a/src/_MarketsPage/datas/MarketMockData.ts b/src/_MarketsPage/datas/MarketMockData.ts deleted file mode 100644 index bb657b6..0000000 --- a/src/_MarketsPage/datas/MarketMockData.ts +++ /dev/null @@ -1,244 +0,0 @@ -import type { MarketItem } from "@/_MarketsPage/types/marketItem"; - -export const MOCK_DATA: MarketItem[] = [ - { - id: "1", - name: "삼성전자", - code: "005930", - price: 82600, - changeRate: 1.1, - tradeVolume: "1.2조", - }, - { - id: "2", - name: "SK하이닉스", - code: "000660", - price: 231000, - changeRate: -1.28, - tradeVolume: "9,824억", - }, - { - id: "3", - name: "한미반도체", - code: "042700", - price: 172000, - changeRate: 5.46, - tradeVolume: "8,123억", - }, - { - id: "4", - name: "LG에너지솔루션", - code: "373220", - price: 345500, - changeRate: 0.0, - tradeVolume: "2,140억", - }, - { - id: "5", - name: "현대차", - code: "005380", - price: 280000, - changeRate: 2.19, - tradeVolume: "4,531억", - }, - { - id: "6", - name: "기아", - code: "000270", - price: 125000, - changeRate: 2.54, - tradeVolume: "3,110억", - }, - { - id: "7", - name: "알테오젠", - code: "196170", - price: 270000, - changeRate: -4.59, - tradeVolume: "5,600억", - }, - { - id: "8", - name: "NAVER", - code: "035420", - price: 195000, - changeRate: -0.75, - tradeVolume: "1.8조", - }, - { - id: "9", - name: "카카오", - code: "035720", - price: 64000, - changeRate: 0.42, - tradeVolume: "7,200억", - }, - { - id: "10", - name: "셀트리온", - code: "068270", - price: 185000, - changeRate: 1.88, - tradeVolume: "6,543억", - }, - { - id: "11", - name: "하이브", - code: "352820", - price: 260000, - changeRate: 0.92, - tradeVolume: "4,312억", - }, - { - id: "12", - name: "카카오뱅크", - code: "323410", - price: 29500, - changeRate: -1.36, - tradeVolume: "5,710억", - }, - { - id: "13", - name: "대한항공", - code: "003490", - price: 24500, - changeRate: 3.24, - tradeVolume: "3,000억", - }, - { - id: "14", - name: "아모레퍼시픽", - code: "090430", - price: 124000, - changeRate: 0.75, - tradeVolume: "1,890억", - }, - { - id: "15", - name: "현대모비스", - code: "012330", - price: 226000, - changeRate: -0.58, - tradeVolume: "2,134억", - }, - { - id: "16", - name: "LG화학", - code: "051910", - price: 540000, - changeRate: 1.63, - tradeVolume: "3,777억", - }, - { - id: "17", - name: "삼성SDI", - code: "006400", - price: 695000, - changeRate: -1.02, - tradeVolume: "2,890억", - }, - { - id: "18", - name: "포스코홀딩스", - code: "005490", - price: 430000, - changeRate: 0.68, - tradeVolume: "3,452억", - }, - { - id: "19", - name: "KT&G", - code: "033780", - price: 92000, - changeRate: -0.45, - tradeVolume: "1,200억", - }, - { - id: "20", - name: "삼성물산", - code: "028260", - price: 138000, - changeRate: 0.25, - tradeVolume: "2,134억", - }, - { - id: "21", - name: "삼성바이오로직스", - code: "207940", - price: 900000, - changeRate: 1.02, - tradeVolume: "1.6조", - }, - { - id: "22", - name: "롯데케미칼", - code: "011170", - price: 175000, - changeRate: -0.93, - tradeVolume: "1,620억", - }, - { - id: "23", - name: "두산에너빌리티", - code: "034020", - price: 20000, - changeRate: 3.45, - tradeVolume: "4,210억", - }, - { - id: "24", - name: "SK이노베이션", - code: "096770", - price: 175000, - changeRate: -1.75, - tradeVolume: "2,960억", - }, - { - id: "25", - name: "한화솔루션", - code: "009830", - price: 51000, - changeRate: 2.31, - tradeVolume: "3,120억", - }, - { - id: "26", - name: "CJ제일제당", - code: "097950", - price: 350000, - changeRate: -0.61, - tradeVolume: "1,450억", - }, - { - id: "27", - name: "LG전자", - code: "066570", - price: 122000, - changeRate: 1.42, - tradeVolume: "2,600억", - }, - { - id: "28", - name: "SK텔레콤", - code: "017670", - price: 55000, - changeRate: -0.15, - tradeVolume: "2,110억", - }, - { - id: "29", - name: "넷마블", - code: "251270", - price: 72000, - changeRate: -2.34, - tradeVolume: "1,800억", - }, - { - id: "30", - name: "엔씨소프트", - code: "036570", - price: 345000, - changeRate: 0.87, - tradeVolume: "2,450억", - }, -]; diff --git a/src/_MarketsPage/types/marketItem.ts b/src/_MarketsPage/types/marketItem.ts index d4cad07..75d90e6 100644 --- a/src/_MarketsPage/types/marketItem.ts +++ b/src/_MarketsPage/types/marketItem.ts @@ -1,8 +1,27 @@ -export type MarketItem = { - id: string; - name: string; - code: string; - price: number; +// API 응답 타입 +export interface StockPriceData { + baseDate: string; + openPrice: number; + highPrice: number; + lowPrice: number; + closePrice: number; + changeAmount: number; changeRate: number; - tradeVolume: string; -}; +} + +export interface StockListItem { + stockName: string; + stockCode: string; + isinCode: string; + listedDate: string; + listedShared: number; + marketCap: number; + stockPriceList: StockPriceData[]; + rank: number; +} + +export interface StockListResponse { + content: StockListItem[]; + totalElements?: number; + totalPages?: number; +} diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx index 7e2208b..35fcf6f 100644 --- a/src/components/Pagination.tsx +++ b/src/components/Pagination.tsx @@ -5,7 +5,20 @@ interface PaginationProps { } const Pagination = ({ currentPage, totalPages, onPageChange }: PaginationProps) => { - const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1); + // 현재 페이지가 속한 5단위 그룹 계산 + const getVisiblePages = () => { + const groupSize = 5; + // 현재 페이지가 속한 그룹 번호 (1부터 시작) + const groupNumber = Math.ceil(currentPage / groupSize); + // 그룹의 시작 페이지 + const startPage = (groupNumber - 1) * groupSize + 1; + // 그룹의 끝 페이지 (totalPages를 넘지 않도록) + const endPage = Math.min(groupNumber * groupSize, totalPages); + + return Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i); + }; + + const visiblePages = getVisiblePages(); return (