Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 34 additions & 23 deletions src/_BacktestingPage/components/AssetAllocation.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import type { Asset } from "@/_BacktestingPage/types/backtestFormType";
import AssetItem from "@/_BacktestingPage/components/AssetItem";
import { v4 as uuidv4 } from "uuid";
import WeightSummary from "@/_BacktestingPage/components/WeightSummary";

type AssetAllocationProps = {
assets: Asset[];
setAssets: React.Dispatch<React.SetStateAction<Asset[]>>;
totalWeight: number;
};

const AssetAllocation = ({ assets, setAssets, totalWeight }: AssetAllocationProps) => {
const handleAddAsset = () => {
setAssets([...assets, { id: uuidv4(), name: "", ticker: "", weight: 0 }]);
Expand All @@ -25,29 +29,36 @@ const AssetAllocation = ({ assets, setAssets, totalWeight }: AssetAllocationProp
};

return (
<div className="relative flex flex-col gap-10 py-8 border-white border-b">
<div className="flex justify-start items-start font-lalezar text-white text-3xl">
자산 설정
</div>
<div className="flex flex-col gap-13">
{assets.map((asset, index) => (
<AssetItem
key={asset.id}
asset={asset}
onUpdate={handleUpdateAsset}
AssetIndex={index}
onDelete={() => handleDeleteAsset(asset.id)}
/>
))}
</div>
<button
onClick={handleAddAsset}
className="relative flex justify-center items-center bg-white mx-auto px-4 py-2 rounded-3xl w-20 font-suit text-navy"
>
+ 추가
</button>
<WeightSummary totalWeight={totalWeight}></WeightSummary>
</div>
<>
<CardHeader className="pt-10">
<CardTitle className="font-semibold text-xl">자산 설정</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-6">
{assets.map((asset, index) => (
<AssetItem
key={asset.id}
asset={asset}
onUpdate={handleUpdateAsset}
AssetIndex={index}
onDelete={() => handleDeleteAsset(asset.id)}
/>
))}
</div>
<div className="flex justify-center items-center mt-6">
<Button
onClick={handleAddAsset}
variant="outline"
className="bg-white/10 hover:opacity-60 px-6 py-2 border-white/20 rounded-xl text-white cursor-pointer"
>
+ 추가
</Button>
</div>
<div className="mt-6">
<WeightSummary totalWeight={totalWeight}></WeightSummary>
</div>
</CardContent>
</>
);
};

Expand Down
19 changes: 9 additions & 10 deletions src/_BacktestingPage/components/AssetItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,11 @@ const AssetItem = ({ AssetIndex, asset, onUpdate, onDelete }: AssetItemProps) =>
}, []);

return (
<div ref={wrapperRef} className="relative flex mx-10">
<div className="relative flex items-center w-55 font-suit text-2xl">
자산 {AssetIndex + 1}
</div>
<div ref={wrapperRef} className="relative flex items-center gap-4">
<div className="w-20 font-medium text-gray-300 text-sm">자산 {AssetIndex + 1}</div>

<input
className="relative flex px-2 py-1 border rounded w-60 h-11"
className="flex-1 bg-white/10 focus:bg-white/15 px-3 py-2 border border-white/20 focus:border-white/30 rounded-lg focus:outline-none h-10 text-white placeholder:text-gray-500 transition-colors"
value={query}
onChange={(e) => {
setQuery(e.target.value);
Expand All @@ -87,21 +85,22 @@ const AssetItem = ({ AssetIndex, asset, onUpdate, onDelete }: AssetItemProps) =>
/>

{isDropdownOpen && searchResults.length > 0 && (
<div className="top-full left-55 z-20 absolute flex flex-col items-center bg-navy mt-2 border rounded w-60 max-h-[240px] overflow-y-auto text-[#E0E6ED] cursor-pointer">
<div className="top-full left-20 z-20 absolute flex flex-col bg-white/95 shadow-xl backdrop-blur-sm mt-2 border border-white/20 rounded-lg w-[calc(100%-5rem)] max-h-[240px] overflow-y-auto">
{searchResults.map((item) => (
<div
key={item.ticker}
className="flex hover:bg-[#182c4d] px-2 py-1 w-full"
className="flex hover:bg-white/10 px-4 py-2 transition-colors cursor-pointer"
onClick={() => handleSelect(item)}
>
{item.name} ({item.ticker})
<span className="text-gray-900">{item.name}</span>
<span className="ml-2 text-gray-600">({item.ticker})</span>
</div>
))}
</div>
)}
<input
type="number"
className="relative flex ml-4 px-2 py-1 border rounded w-24 h-11"
className="bg-white/10 focus:bg-white/15 px-3 py-2 border border-white/20 focus:border-white/30 rounded-lg focus:outline-none w-24 h-10 text-white placeholder:text-gray-500 transition-colors"
value={asset.weight === 0 ? "" : asset.weight}
onChange={(e) => {
const newWeight = Math.max(0, Math.min(100, Number(e.target.value)));
Expand All @@ -111,7 +110,7 @@ const AssetItem = ({ AssetIndex, asset, onUpdate, onDelete }: AssetItemProps) =>
/>
{AssetIndex !== 0 ? (
<button
className="flex justify-center items-center hover:bg-blue-950 px-4 py-1 rounded-3xl font-suit text-l text-white"
className="flex justify-center items-center hover:bg-white/10 px-4 py-2 border border-white/20 rounded-lg h-10 text-white transition-colors"
onClick={onDelete}
>
X
Expand Down
111 changes: 111 additions & 0 deletions src/_BacktestingPage/components/BacktestChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
import type { TooltipProps } from "recharts";
import type { MonthlyData } from "@/_BacktestingPage/types/backtestFormType";
import { formatNumber } from "@/lib/utils";

interface BacktestChartProps {
data: MonthlyData[];
label: string;
color?: string;
valueFormatter?: (value: number) => string;
}

interface ChartDataPoint {
date: string;
value: number;
}

type CustomTooltipProps = TooltipProps<number, string> & {
payload?: Array<{
value: number;
payload: ChartDataPoint;
}>;
};

const BacktestChart = ({
data,
label,
color = "#3b82f6",
valueFormatter = (value) => formatNumber(value),
}: BacktestChartProps) => {
// 날짜 포맷팅 (YYYY-MM-DD -> MM/DD)
const formatDate = (dateString: string): string => {
const date = new Date(dateString);
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${month}/${day}`;
};

// 차트 데이터 변환
const chartData = data.map((item) => ({
date: formatDate(item.date),
value: item.value,
}));

// 커스텀 툴팁
const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
if (active && payload && payload.length > 0) {
const dataPoint = payload[0];
if (dataPoint && dataPoint.payload) {
return (
<div className="bg-white/90 shadow-lg backdrop-blur-sm p-3 border border-white/20 rounded-lg">
<p className="mb-1 font-semibold text-gray-900">{dataPoint.payload.date}</p>
<p className="font-medium text-gray-700">
{label}: <span className="text-gray-900">{valueFormatter(dataPoint.value)}</span>
</p>
</div>
);
}
}
return null;
};

return (
<Card className="bg-white/5 border-white/10 text-white">
<CardHeader>
<CardTitle className="font-semibold text-xl">{label}</CardTitle>
</CardHeader>
<CardContent>
<div className="w-full h-80">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={chartData} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255, 255, 255, 0.1)" />
<XAxis
dataKey="date"
stroke="#9aa0a6"
style={{ fontSize: "12px" }}
tick={{ fill: "#9aa0a6" }}
/>
<YAxis
stroke="#9aa0a6"
style={{ fontSize: "12px" }}
tick={{ fill: "#9aa0a6" }}
tickFormatter={valueFormatter}
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="value"
stroke={color}
strokeWidth={2}
dot={{ fill: color, r: 4 }}
activeDot={{ r: 6 }}
/>
</LineChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
);
};

export default BacktestChart;
Loading