Skip to content

Commit ab7b9ff

Browse files
authored
Merge pull request #49 from TeamStockPort/feat/#48-asset-info-api
[Feat/#48] 주가 정보 데이터 API 연동
2 parents aea67dd + 90837c2 commit ab7b9ff

15 files changed

Lines changed: 341 additions & 361 deletions

File tree

src/_MarketDetailPage/components/ChartFilterBar.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
22
import { ChartCandlestick, ChartLine } from "lucide-react";
3+
import { type Period, type ChartType } from "@/_MarketDetailPage/types/stockDataType";
34

45
interface ChartFilterBarProps {
6+
period: Period;
7+
chartType: ChartType;
58
onChangePeriod: (value: string) => void;
69
onChangeChartType: (value: string) => void;
710
}
811

9-
const ChartFilterBar = ({ onChangePeriod, onChangeChartType }: ChartFilterBarProps) => {
12+
const ChartFilterBar = ({
13+
period,
14+
chartType,
15+
onChangePeriod,
16+
onChangeChartType,
17+
}: ChartFilterBarProps) => {
1018
return (
1119
<div className="flex justify-start items-center gap-4 ml-2.5 h-20">
12-
<Tabs defaultValue="1M" onValueChange={onChangePeriod}>
20+
<Tabs value={period} onValueChange={onChangePeriod}>
1321
<TabsList className="bg-white/5 border-white/10">
1422
<TabsTrigger
1523
value="1W"
@@ -37,7 +45,7 @@ const ChartFilterBar = ({ onChangePeriod, onChangeChartType }: ChartFilterBarPro
3745
</TabsTrigger>
3846
</TabsList>
3947
</Tabs>
40-
<Tabs defaultValue="candlestick" onValueChange={onChangeChartType}>
48+
<Tabs value={chartType} onValueChange={onChangeChartType}>
4149
<TabsList className="bg-white/5 border-white/10">
4250
<TabsTrigger
4351
value="candlestick"

src/_MarketDetailPage/components/StockChart.tsx

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,30 @@
1-
import ChartFilterBar from "@/_MarketDetailPage/components/ChartFilterBar";
21
import ReactECharts from "echarts-for-react";
3-
import {
4-
type ChartType,
5-
type Period,
6-
type PriceHistory,
7-
} from "@/_MarketDetailPage/types/stockDataType";
8-
import { useState, useRef } from "react";
2+
import { type ChartType, type Period } from "@/_MarketDetailPage/types/stockDataType";
3+
import { useRef, useEffect, useMemo } from "react";
4+
import { useGetStockDetail } from "@/lib/hooks/useGetStockDetail";
5+
import { Spinner } from "@/components/ui/spinner";
96

107
interface StockChartProps {
11-
stockData: PriceHistory[];
8+
stockCode: string;
9+
period: Period;
10+
chartType: ChartType;
1211
}
1312

14-
const StockChart = ({ stockData }: StockChartProps) => {
15-
const [period, setPeriod] = useState<Period>("1M");
16-
const [chartType, setChartType] = useState<ChartType>("candlestick");
13+
const StockChart = ({ stockCode, period, chartType }: StockChartProps) => {
1714
const isFirstRender = useRef(true);
1815

19-
const handleChangePeriod = (value: string) => {
20-
setPeriod(value as Period);
21-
isFirstRender.current = false;
22-
};
16+
// period에 해당하는 API 요청
17+
const { data: stockData, isLoading } = useGetStockDetail(stockCode, period);
2318

24-
const handleChangeChartType = (value: string) => {
25-
setChartType(value as ChartType);
19+
useEffect(() => {
2620
isFirstRender.current = false;
27-
};
21+
}, [chartType, period]);
2822

2923
const formatDate = (dateString: string) => {
30-
const year = dateString.substring(0, 4);
31-
const month = dateString.substring(4, 6);
32-
const day = dateString.substring(6, 8);
24+
const parts = dateString.split("-");
25+
const year = parts[0];
26+
const month = parts[1];
27+
const day = parts[2];
3328

3429
if (period === "10Y") {
3530
return `${year}.${month}`;
@@ -38,15 +33,34 @@ const StockChart = ({ stockData }: StockChartProps) => {
3833
}
3934
};
4035

41-
const dates = stockData.map((item) => formatDate(item.baseDate));
42-
const candleData = stockData.map((item) => [
36+
// period에 따라 데이터 필터링 (useMemo는 early return 전에 호출)
37+
const filteredDataByPeriod = useMemo(() => {
38+
if (!stockData || !stockData.stockPriceList) {
39+
return [];
40+
}
41+
42+
// 모든 데이터를 역순으로 반환 (10년 데이터도 모두 렌더링)
43+
return [...stockData.stockPriceList].reverse();
44+
}, [stockData, period]);
45+
46+
// API 데이터가 없으면 로딩 표시
47+
if (isLoading || !stockData || !stockData.stockPriceList || filteredDataByPeriod.length === 0) {
48+
return (
49+
<div className="flex justify-center items-center h-500">
50+
<Spinner className="size-12" />
51+
</div>
52+
);
53+
}
54+
55+
const dates = filteredDataByPeriod.map((item) => formatDate(item.baseDate));
56+
const candleData = filteredDataByPeriod.map((item) => [
4357
item.openPrice,
4458
item.closePrice,
4559
item.lowPrice,
4660
item.highPrice,
4761
]);
48-
const lows = stockData.map((d) => d.lowPrice);
49-
const highs = stockData.map((d) => d.highPrice);
62+
const lows = filteredDataByPeriod.map((d) => d.lowPrice);
63+
const highs = filteredDataByPeriod.map((d) => d.highPrice);
5064

5165
const option = {
5266
animation: isFirstRender.current,
@@ -99,7 +113,10 @@ const StockChart = ({ stockData }: StockChartProps) => {
99113
},
100114
series: {
101115
type: chartType === "candlestick" ? "candlestick" : "line",
102-
data: chartType === "candlestick" ? candleData : stockData.map((item) => item.closePrice),
116+
data:
117+
chartType === "candlestick"
118+
? candleData
119+
: filteredDataByPeriod.map((item) => item.closePrice),
103120
smooth: false,
104121
itemStyle:
105122
chartType === "candlestick"
@@ -138,10 +155,6 @@ const StockChart = ({ stockData }: StockChartProps) => {
138155
};
139156
return (
140157
<div className="flex-col gap-20 m-10 w-full">
141-
<ChartFilterBar
142-
onChangePeriod={handleChangePeriod}
143-
onChangeChartType={handleChangeChartType}
144-
/>
145158
<ReactECharts option={option} style={{ height: 500 }} />
146159
</div>
147160
);

src/_MarketDetailPage/datas/stockSample.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const sampleData: StockData = {
99
listedShares: 5969782550,
1010
marketCap: 437200000000000,
1111
currentPrice: 109800,
12-
priceHistory: [
12+
stockPriceList: [
1313
{
1414
baseDate: "20251021",
1515
openPrice: 108000,

src/_MarketDetailPage/types/stockDataType.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export interface PriceHistory {
1+
export interface StockPriceList {
22
baseDate: string;
33
openPrice: number;
44
closePrice: number;
@@ -17,7 +17,7 @@ export interface StockData {
1717
listedShares: number;
1818
marketCap: number;
1919
currentPrice: number;
20-
priceHistory: PriceHistory[];
20+
stockPriceList: StockPriceList[];
2121
}
2222

2323
export type Period = "1W" | "1M" | "1Y" | "10Y";

src/_MarketsPage/components/MarketList.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import type { MarketItem } from "@/_MarketsPage/types/marketItem";
1+
import type { StockListItem } from "@/_MarketsPage/types/marketItem";
22
import { useNavigate } from "react-router-dom";
3+
import { formatNumber } from "@/lib/utils";
34

45
interface MarketListProps {
5-
items: MarketItem[];
6+
items: StockListItem[];
67
currentPage: number;
78
itemsPerPage: number;
89
}
@@ -24,39 +25,41 @@ const MarketList = ({ items, currentPage, itemsPerPage }: MarketListProps) => {
2425
<th className="p-4 w-40 font-normal text-gray-400 text-sm text-left">자산명</th>
2526
<th className="p-4 w-32 font-normal text-gray-400 text-sm text-left">현재가</th>
2627
<th className="p-4 w-32 font-normal text-gray-400 text-sm text-left">등락률</th>
27-
<th className="p-4 w-32 font-normal text-gray-400 text-sm text-left">거래대금</th>
28+
<th className="p-4 w-32 font-normal text-gray-400 text-sm text-left">시가총액</th>
2829
</tr>
2930
</thead>
3031
<tbody>
3132
{items.map((item, index) => {
32-
const { className, icon } = getChangeInfo(item.changeRate);
33+
const latestPrice = item.stockPriceList?.[0];
34+
const changeRate = latestPrice?.changeRate ?? 0;
35+
const { className, icon } = getChangeInfo(changeRate);
3336
return (
3437
<tr
35-
key={item.id}
38+
key={item.stockCode}
3639
className="hover:bg-white/5 border-white/10 border-b cursor-pointer"
37-
onClick={() => navigate(`/markets/${item.code}`)}
40+
onClick={() => navigate(`/markets/${item.stockCode}`)}
3841
>
3942
{/* 순번 */}
4043
<td className="p-4 text-gray-400 align-middle">{startIndex + index + 1}</td>
4144

4245
{/* 종목명과 코드 */}
4346
<td className="p-4 align-middle">
44-
<div className="font-bold">{item.name}</div>
45-
<div className="text-gray-400 text-sm">{item.code}</div>
47+
<div className="font-bold">{item.stockName}</div>
48+
<div className="text-gray-400 text-sm">{item.stockCode}</div>
4649
</td>
4750

4851
{/* 현재가 */}
4952
<td className={`p-4 align-middle font-bold ${className}`}>
50-
{item.price.toLocaleString()}
53+
{formatNumber(latestPrice?.closePrice ?? 0)}
5154
</td>
5255

5356
{/* 등락률 */}
5457
<td className={`p-4 align-middle ${className}`}>
55-
{icon} {Math.abs(item.changeRate).toFixed(2)}%
58+
{icon} {Math.abs(changeRate).toFixed(2)}%
5659
</td>
5760

58-
{/* 거래대금 */}
59-
<td className="p-4 align-middle">{item.tradeVolume}</td>
61+
{/* 시가총액 */}
62+
<td className="p-4 align-middle">{formatNumber(item.marketCap)}</td>
6063
</tr>
6164
);
6265
})}

0 commit comments

Comments
 (0)