"use client" import * as React from "react" import { TrendingUp, TrendingDown, RefreshCw, Activity, DollarSign, BarChart2, Layers } from "lucide-react" import { StatCard } from "@/components/stat-card" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils" interface Position { key: string tradingsymbol: string exchange: string instrumenttype: string producttype: string netqty: number ltp: number avg_price: number unrealised_pnl: number realised_pnl: number total_pnl: number is_closed: number updated_at: string } interface Summary { totalUnrealised: number totalRealised: number totalPnl: number openPositions: number asOf?: string } function fmt(n: number) { const sign = n >= 0 ? "+" : "" return `${sign}₹${Math.abs(n).toLocaleString("en-IN", { maximumFractionDigits: 0 })}` } function PnlCell({ value }: { value: number }) { return ( 0 ? "text-emerald-500" : value < 0 ? "text-rose-500" : "text-muted-foreground")}> {fmt(value)} ) } export default function PositionsPage() { const [positions, setPositions] = React.useState([]) const [summary, setSummary] = React.useState(null) const [loading, setLoading] = React.useState(true) const [refreshing, setRefreshing] = React.useState(false) const [error, setError] = React.useState(null) const [lastUpdated, setLastUpdated] = React.useState(null) const load = React.useCallback(async (silent = false) => { if (!silent) setLoading(true) setError(null) try { const res = await fetch("/api/positions", { cache: "no-store" }) const data = await res.json() if (!data.ok) throw new Error(data.error ?? "Failed to load") setPositions(data.positions ?? []) setSummary(data.summary ?? null) setLastUpdated(new Date()) } catch (e: any) { setError(e.message) } finally { setLoading(false) } }, []) const handleRefresh = async () => { setRefreshing(true) try { await fetch("/api/positions", { method: "POST" }) } catch { /* ignore */ } await load(true) setRefreshing(false) } React.useEffect(() => { load() const id = setInterval(() => load(true), 30_000) return () => clearInterval(id) }, [load]) const open = positions.filter(p => p.netqty !== 0 && !p.is_closed) const closed = positions.filter(p => p.netqty === 0 && p.is_closed && p.realised_pnl !== 0) return (

Positions

{lastUpdated ? `Updated ${lastUpdated.toLocaleTimeString("en-IN", { timeZone: "Asia/Kolkata", hour12: false })} IST` : "Live positions from Angel One"}

{error && ( {error} )} {/* Summary stat cards */}
= 0 ? TrendingUp : TrendingDown} className={summary && summary.totalPnl < 0 ? "border-rose-500/30" : "border-emerald-500/30"} /> 0 ? `${closed.length} closed today` : undefined} />
{/* Open positions table */} Open Positions ({open.length}) {loading ? (
Loading…
) : open.length === 0 ? (
No open positions
) : (
{open.map(p => ( ))}
Symbol Qty Avg LTP Unrealised Total P&L Type
{p.tradingsymbol} 0 ? "text-emerald-500" : "text-rose-500"}> {p.netqty > 0 ? "+" : ""}{p.netqty} ₹{p.avg_price.toFixed(2)} ₹{p.ltp.toFixed(2)} {p.instrumenttype || p.producttype}
)}
{/* Closed today */} {!loading && closed.length > 0 && ( Closed Today ({closed.length})
{closed.map(p => ( ))}
Symbol Realised P&L Type
{p.tradingsymbol} {p.instrumenttype || p.producttype}
)}
) }