From 0142b1bfe79a6506e6d3d71bb3564fe02b08e4f0 Mon Sep 17 00:00:00 2001 From: Manohar Date: Wed, 10 Jun 2026 14:59:41 +0000 Subject: [PATCH] refactor(dashboard): /positions hands off to the dedicated tracker angel.manohargupta.com (standalone position-tracker) is the single owner of live positions UI; the replicated page here drifted against the same Angel One data. Page now auto-redirects with a fallback link. --- dashboard/src/app/positions/page.tsx | 251 ++++----------------------- 1 file changed, 34 insertions(+), 217 deletions(-) diff --git a/dashboard/src/app/positions/page.tsx b/dashboard/src/app/positions/page.tsx index 4a528ab..c7f419e 100644 --- a/dashboard/src/app/positions/page.tsx +++ b/dashboard/src/app/positions/page.tsx @@ -1,230 +1,47 @@ "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" +/** + * /positions — intentionally NOT a positions UI. + * + * Live positions have a dedicated home: the standalone position-tracker at + * https://angel.manohargupta.com (its own repo, own deploy, market-hours + * aware). This dashboard previously replicated that UI here, which meant + * two implementations drifting against the same Angel One data. One owner + * per concern: the tracker owns positions; this page just hands you over. + */ -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 -} +import { useEffect } from "react" +import { ExternalLink, TrendingUp } from "lucide-react" +import { Card } from "@/components/ui/card" -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)} - - ) -} +const TRACKER_URL = "https://angel.manohargupta.com" 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) - } + useEffect(() => { + // Auto-redirect after a beat — the card below is the no-JS / slow-net fallback. + const t = setTimeout(() => { + window.location.href = TRACKER_URL + }, 800) + return () => clearTimeout(t) }, []) - 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 => ( - - - - - - - - - - ))} - -
SymbolQtyAvgLTPUnrealisedTotal P&LType
{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} - -
-
- )} -
+
+ + +

Positions live on the tracker

+

+ Redirecting you to the dedicated position tracker — single source of + truth for live P&L, bands and alerts. +

+ + Open angel.manohargupta.com + +
- - {/* Closed today */} - {!loading && closed.length > 0 && ( - - - Closed Today ({closed.length}) - - -
- - - - - - - - - - {closed.map(p => ( - - - - - - ))} - -
SymbolRealised P&LType
{p.tradingsymbol} - - {p.instrumenttype || p.producttype} - -
-
-
-
- )}
) }