Recreate Next.js source — shadcn components, chart, types, hooks
Source files were untracked and lost during reset. Recreated: - page.tsx (full 3-tab dashboard), layout.tsx, globals.css (shadcn theme) - types.ts, api.ts, utils.ts - strategy-card.tsx, equity-chart.tsx, positions-panel.tsx - ui/ (7 shadcn wrappers: card, badge, button, table, tabs, sheet, collapsible, select) - Config files: next.config.ts, tsconfig.json, postcss.config.mjs, components.json
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { Strategy } from "@/lib/types";
|
||||
import { TrendingUp, TrendingDown } from "lucide-react";
|
||||
|
||||
interface StrategyCardProps {
|
||||
name: string;
|
||||
strategy?: Strategy;
|
||||
tab: "live" | "paper" | "backtest" | "historical";
|
||||
onClick: () => void;
|
||||
badge?: string;
|
||||
stats?: { label: string; value: string; negative?: boolean }[];
|
||||
pnlPct?: number;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export function StrategyCard({ name, strategy, tab, onClick, badge, stats, pnlPct, status }: StrategyCardProps) {
|
||||
if (strategy) {
|
||||
const equity = strategy.allocation + (strategy.pnl ?? 0);
|
||||
const isUp = equity >= strategy.allocation;
|
||||
const pnl = strategy.pnl ?? 0;
|
||||
const pnlPctVal = strategy.pnl_pct ?? 0;
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="p-4 cursor-pointer hover:border-primary/50 hover:shadow-md transition-all duration-200 hover:-translate-y-0.5 border-border"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<p className="text-xs font-semibold leading-tight">{name}</p>
|
||||
<p className="text-[9px] text-muted-foreground mt-0.5">
|
||||
${strategy.allocation} · {strategy.type}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Badge variant={strategy.status === "running" ? "default" : "secondary"} className="text-[8px] h-4 px-1.5">
|
||||
{strategy.status?.toUpperCase()}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-[8px] h-4 px-1.5 border-border">
|
||||
{strategy.fee_model?.toUpperCase()}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={`text-xl font-mono font-bold mb-2 flex items-center gap-1 ${isUp ? "text-green-500" : "text-red-500"}`}>
|
||||
{isUp ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
|
||||
${equity.toFixed(2)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 text-[9px] text-muted-foreground flex-wrap">
|
||||
<span>PnL: <b className={pnlPctVal >= 0 ? "text-green-500" : "text-red-500"}>{pnl >= 0 ? "+" : ""}{pnl.toFixed(2)} ({pnlPctVal >= 0 ? "+" : ""}{pnlPctVal.toFixed(2)}%)</b></span>
|
||||
<span>Trades: <b>{strategy.trades_today ?? 0}</b></span>
|
||||
<span>Win: <b>{Math.round((strategy.win_rate ?? 0) * 100)}%</b></span>
|
||||
<span>Pos: <b>{(strategy.position ?? 0).toFixed(4)}</b></span>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-border/50 text-[9px] text-muted-foreground leading-relaxed">
|
||||
<b>Alloc:</b> ${strategy.allocation} · <b>Max pos:</b> {strategy.max_position ?? "—"} · <b>Stop:</b> {strategy.stop_loss ?? "—"}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Backtest / Historical card
|
||||
return (
|
||||
<Card
|
||||
className="p-4 cursor-pointer hover:border-primary/50 hover:shadow-md transition-all duration-200 hover:-translate-y-0.5 border-border"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div>
|
||||
<p className="text-xs font-semibold leading-tight">{name}</p>
|
||||
<p className="text-[9px] text-muted-foreground mt-0.5">{badge ?? "Backtest"}</p>
|
||||
</div>
|
||||
<Badge variant="secondary" className="text-[8px] h-4 px-1.5">{status ?? "BACKTEST"}</Badge>
|
||||
</div>
|
||||
<div className={`text-xl font-mono font-bold mb-2 flex items-center gap-1 ${(pnlPct ?? 0) >= 0 ? "text-green-500" : "text-red-500"}`}>
|
||||
{(pnlPct ?? 0) >= 0 ? <TrendingUp className="w-4 h-4" /> : <TrendingDown className="w-4 h-4" />}
|
||||
{(pnlPct ?? 0) >= 0 ? "+" : ""}{(pnlPct ?? 0).toFixed(2)}%
|
||||
</div>
|
||||
{stats && (
|
||||
<div className="flex gap-3 text-[9px] text-muted-foreground flex-wrap">
|
||||
{stats.map((s) => (
|
||||
<span key={s.label}>
|
||||
{s.label}: <b className={s.negative ? "text-red-500" : ""}>{s.value}</b>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user