diff --git a/src/app/my-account/_components/stock-summary.tsx b/src/app/my-account/_components/stock-summary.tsx new file mode 100644 index 0000000..c083a91 --- /dev/null +++ b/src/app/my-account/_components/stock-summary.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { clsx } from "clsx"; + +import { TotalStocks } from "../types"; + +interface StockSummaryProps { + totalStocks: TotalStocks; +} + +export default function StockSummary({ totalStocks }: StockSummaryProps) { + const getValueWithSign = (value: number | null | undefined) => { + if (!value) return "0"; + return `${value > 0 ? "+" : ""}${value.toLocaleString()}`; + }; + + return ( +
+ + + + + + + + + +
+ 닉네임 + + {totalStocks?.memberNickname} + + 예수금 + + {totalStocks?.deposit?.toLocaleString() ?? 0} +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ 총 평가 손익 + 0, + "text-blue-500": + (totalStocks?.totalEvaluationProfit ?? 0) < 0, + }, + )} + > + {getValueWithSign(totalStocks?.totalEvaluationProfit)} +
+ 총 평가 금액 + + {totalStocks?.totalEvaluationAmount?.toLocaleString() ?? 0} + + 총 매입 금액 + + {totalStocks?.totalPurchaseAmount?.toLocaleString() ?? 0} +
+ 추정 자산 + + {totalStocks?.estimatedAsset?.toLocaleString() ?? 0} +
+ 랭킹 + + {totalStocks?.rank ?? 0}등 +
+
+ ); +} diff --git a/src/app/my-account/_components/stock-table.tsx b/src/app/my-account/_components/stock-table.tsx new file mode 100644 index 0000000..44dae35 --- /dev/null +++ b/src/app/my-account/_components/stock-table.tsx @@ -0,0 +1,135 @@ +"use client"; + +import clsx from "clsx"; +import { useId, useMemo } from "react"; + +import { Stock } from "../types"; + +interface StockTableProps { + stocks: Stock[] | null | undefined; +} + +export default function StockTable({ stocks }: StockTableProps) { + const tableId = useId(); + + const stockRows = useMemo(() => { + if (!stocks || !Array.isArray(stocks)) return []; + return stocks.map((stock) => ({ + ...stock, + id: `${tableId}-${stock.stockName}`, + })); + }, [stocks, tableId]); + + // 수익률의 부호에 따라 평가손익의 부호를 보정 + const getEvaluationProfitWithSign = (profit: number, rate: number) => { + const absProfit = Math.abs(profit); + return rate < 0 ? -absProfit : absProfit; + }; + + return ( +
+ + + + + + + + + + + + + + + + + {stockRows.map((stock) => ( + + + + + + + + + ))} + +
+ 종목명 + + 평가손익 + + 보유수량 + + 평가금액 +
+ 금액 + + 수익률 + + 매입가 + 현재가
+ {stock.stockName} + 0, + "text-blue-500": stock.ProfitRate < 0, + }, + )} + > + {getEvaluationProfitWithSign( + stock.EvaluationProfit, + stock.ProfitRate, + ) > 0 && "+"} + {getEvaluationProfitWithSign( + stock.EvaluationProfit, + stock.ProfitRate, + ).toLocaleString()} + 0, + "text-blue-500": stock.ProfitRate < 0, + }, + )} + > + {stock.ProfitRate > 0 && "+"} + {stock.ProfitRate?.toFixed(2) ?? 0} % + + {stock.stockCount ?? 0} + + {stock.purchaseAmount?.toLocaleString() ?? 0} + +
+ {stock.currentPrice?.toLocaleString() ?? 0} +
+
0, + "text-blue-500": stock.prevChangeRate < 0, + })} + > + 어제보다 + {stock.prevChangeRate > 0 && "+"} + {stock.prevChangeRate?.toFixed(2) ?? 0} % +
+
+
+ ); +} diff --git a/src/app/my-account/layout.tsx b/src/app/my-account/layout.tsx new file mode 100644 index 0000000..bc71184 --- /dev/null +++ b/src/app/my-account/layout.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "내 계좌 페이지 | 온라인 투자플랫폼", + description: "내 계좌 페이지입니다", +}; + +export default function Layout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+ {children} +
+ ); +} diff --git a/src/app/my-account/page.tsx b/src/app/my-account/page.tsx new file mode 100644 index 0000000..e73c2e4 --- /dev/null +++ b/src/app/my-account/page.tsx @@ -0,0 +1,60 @@ +import { getCookie } from "@/utils/next-cookies"; + +import StockSummary from "./_components/stock-summary"; +import StockTable from "./_components/stock-table"; + +async function getStocks() { + try { + const token = await getCookie("token"); + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/account/stocks`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + return await response.json(); + } catch (error) { + console.error("주식 데이터 조회 실패:", error); //eslint-disable-line + return []; + } +} + +async function getTotalStocks() { + try { + const token = await getCookie("token"); + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/account/all-stocks`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + return await response.json(); + } catch (error) { + console.error("총 주식 데이터 조회 실패:", error); //eslint-disable-line + return { + totalEvaluationProfit: 0, + totalPurchaseAmount: 0, + totalProfit: 0, + totalEvaluationAmount: 0, + }; + } +} + +export default async function StockPortfolioPage() { + const stocks = await getStocks(); + const totalStocks = await getTotalStocks(); + + return ( +
+

내 계좌

+
+ + +
+
+ ); +} diff --git a/src/app/my-account/types/index.ts b/src/app/my-account/types/index.ts new file mode 100644 index 0000000..90553a0 --- /dev/null +++ b/src/app/my-account/types/index.ts @@ -0,0 +1,19 @@ +export interface Stock { + stockName: string; // 종목명 + currentPrice: number; // 현재가 + stockCount: number; // 보유수량 + prevChangeRate: number; // 전일 대비 등락률 + EvaluationProfit: number; // 평가손익 + ProfitRate: number; // 수익률 + purchaseAmount: number; // 매입단가 +} + +export interface TotalStocks { + memberNickname: string; + deposit: number; // 예수금 + totalEvaluationProfit: number; // 총 평가손익 + totalPurchaseAmount: number; // 총 매입금액 + totalEvaluationAmount: number; // 총 평가금액 + estimatedAsset: number; // 추정자산 + rank: number; // 순위 +} diff --git a/src/app/page.tsx b/src/app/page.tsx index b44a5d6..b8e41c8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -41,9 +41,9 @@ export default async function Home() { ]); return ( -
+
{/* 메인 컨텐츠 */} -
+
diff --git a/src/components/nav-bar/_components/nav-menu.tsx b/src/components/nav-bar/_components/nav-menu.tsx index 8c71755..31f06ce 100644 --- a/src/components/nav-bar/_components/nav-menu.tsx +++ b/src/components/nav-bar/_components/nav-menu.tsx @@ -30,7 +30,7 @@ const NAV_ITEMS = [ activeIcon: ChartActiveIcon, }, { - href: "/mypage", + href: "/my-account", name: "내 계좌", icon: AccountIcon, activeIcon: AccountActiveIcon, diff --git a/tailwind.config.ts b/tailwind.config.ts index 0ec6fa6..1873fbf 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -15,8 +15,8 @@ const config: Config = { screens: { sm: { max: "375px" }, md: { min: "744px" }, - lg: { min: "1200px" }, - xl: { min: "1280px" }, + lg: { min: "1440px" }, + xl: { min: "1900px" }, }, extend: { colors: {},