@@ -100,15 +121,28 @@ const BacktestingPage = () => {
totalWeight={totalWeight}
>
-
+
+ {/* 목데이터 버튼 (백엔드 연결 시 아래 주석 해제하고 이 버튼 제거) */}
+
+
+ 백테스트 시작 (삼성전자 60% + SK하이닉스 40%, 5년)
+
+
+
+ {/* 실제 API 버튼 (백엔드 연결 시 주석 해제) */}
+ {/*
-
+
*/}
- {/* 로딩 상태 또는 Progress 진행 중 - 전체 화면 overlay */}
- {(isPending || (progress > 0 && progress < 100)) && (
+ {/* 실제 API 로딩 상태 (백엔드 연결 시 주석 해제) */}
+ {/* {(isPending || (progress > 0 && progress < 100)) && (
@@ -121,10 +155,10 @@ const BacktestingPage = () => {
- )}
+ )} */}
- {/* 에러 상태 - error가 있거나 data.isSuccess가 false인 경우 */}
- {hasError && !isPending && progress === 0 && (
+ {/* 실제 API 에러 상태 (백엔드 연결 시 주석 해제) */}
+ {/* {hasError && !isPending && progress === 0 && (
@@ -139,14 +173,23 @@ const BacktestingPage = () => {
+ )} */}
+
+ {/* 목데이터 결과 */}
+ {mockResult && lastRequest && (
+
+
+
+
)}
- {/* 성공 상태 - Progress가 100%가 되고 showResult가 true일 때만 렌더링 */}
- {showResult && !error && data?.isSuccess && data?.result && (
+ {/* 실제 API 결과 (백엔드 연결 시 주석 해제하고 위 목데이터 결과 제거) */}
+ {/* {showResult && !error && data?.isSuccess && data?.result && (
+ {lastRequest && }
- )}
+ )} */}
);
};
diff --git a/src/pages/PortfolioDetailPage.tsx b/src/pages/PortfolioDetailPage.tsx
new file mode 100644
index 0000000..eea46ac
--- /dev/null
+++ b/src/pages/PortfolioDetailPage.tsx
@@ -0,0 +1,79 @@
+import { useParams, useNavigate } from "react-router-dom";
+import { useBacktestDetail, useDeleteBacktest } from "@/lib/hooks/useBacktestStorage";
+import BacktestResult from "@/_BacktestingPage/components/BacktestResult";
+import Title from "@/components/Title";
+import { Spinner } from "@/components/ui/spinner";
+import { Button } from "@/components/ui/button";
+import { ArrowLeft, Trash2 } from "lucide-react";
+import { toast } from "sonner";
+
+const PortfolioDetailPage = () => {
+ const { backtestId } = useParams<{ backtestId: string }>();
+ const navigate = useNavigate();
+ const { data, isLoading, error } = useBacktestDetail(backtestId ?? "");
+ const { mutate: deleteBacktest, isPending: isDeleting } = useDeleteBacktest();
+
+ const handleDelete = () => {
+ if (!backtestId || !confirm("정말 삭제하시겠습니까?")) return;
+
+ deleteBacktest(backtestId, {
+ onSuccess: () => {
+ toast.success("삭제되었습니다.");
+ navigate("/portfolio");
+ },
+ onError: () => toast.error("삭제에 실패했습니다."),
+ });
+ };
+
+ if (isLoading) {
+ return (
+
-
-
-
-
-
- 포트폴리오가 없습니다 :(
-
- 아직 수행한 백테스팅이 없어요.
- 아래 버튼을 통해 백테스팅을 수행해보세요.
-
-
-
-
-
- 백테스팅 시작하기
-
-
-
-
-
+
+
+
+
+
+
+
+ #
+ 포트폴리오명
+ 투자금
+ 최종자산
+ CAGR
+ 생성일
+ 삭제
+
+
+
+ {backtests.map((item, index) => {
+ const summary = item.result.portfolioSummary;
+ const cagrClass =
+ summary.cagr > 0
+ ? "text-red-500"
+ : summary.cagr < 0
+ ? "text-blue-500"
+ : "text-white";
+
+ return (
+ navigate(`/portfolio/${item.id}`)}
+ >
+ {index + 1}
+
+ {item.title}
+
+ {item.request.startDate} ~ {item.request.endDate}
+
+
+ {formatNumber(summary.initialCapital)}원
+
+ {formatNumber(summary.finalCapital)}원
+
+
+ {summary.cagr > 0 ? "+" : ""}
+ {summary.cagr.toFixed(2)}%
+
+
+ {new Date(item.created_at).toLocaleDateString("ko-KR")}
+
+
+ handleDelete(e, item.id)}
+ className="hover:text-red-400 text-gray-500 transition-colors cursor-pointer"
+ >
+
+
+
+
+ );
+ })}
+
+
+
);
};
diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx
index d33a0a5..ca219c6 100644
--- a/src/routes/routes.tsx
+++ b/src/routes/routes.tsx
@@ -3,6 +3,7 @@ import Layout from "@/layouts/Layout";
import { MainPage, MarketsPage, MarketDetailPage, PortfolioPage, BacktestingPage } from "@/pages";
import LoginPage from "@/pages/LoginPage";
import SignupPage from "@/pages/SignupPage";
+import PortfolioDetailPage from "@/pages/PortfolioDetailPage";
export const routes: RouteObject[] = [
{
@@ -21,6 +22,7 @@ export const routes: RouteObject[] = [
{ path: "markets", element:
},
{ path: "markets/:code", element:
},
{ path: "portfolio", element:
},
+ { path: "portfolio/:backtestId", element:
},
{ path: "backtest", element:
},
],
},
diff --git a/supabase/backtest_results.sql b/supabase/backtest_results.sql
new file mode 100644
index 0000000..86f3f98
--- /dev/null
+++ b/supabase/backtest_results.sql
@@ -0,0 +1,23 @@
+create table backtest_results (
+ id uuid default gen_random_uuid() primary key,
+ user_id uuid references auth.users(id) on delete cascade not null,
+ title text not null,
+ request jsonb not null,
+ result jsonb not null,
+ created_at timestamptz default now() not null
+);
+
+-- RLS: 본인 데이터만 접근
+alter table backtest_results enable row level security;
+
+create policy "Users can insert own data"
+ on backtest_results for insert
+ with check (auth.uid() = user_id);
+
+create policy "Users can select own data"
+ on backtest_results for select
+ using (auth.uid() = user_id);
+
+create policy "Users can delete own data"
+ on backtest_results for delete
+ using (auth.uid() = user_id);