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 (