Show stage pill next to profit target on dashboards
State API now returns stage = 1 + number of withdrawals. Both the main dashboard and the account detail page show a small Stage N pill next to the profit target value. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
009f7471c2
commit
536aad27ef
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, type ReactNode } from 'react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import {
|
import {
|
||||||
@@ -47,7 +47,7 @@ function PerfRow({ label, value, badge, positive }: { label: string; value: stri
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ObjRow({ passed, label, value }: { passed: boolean; label: string; value: string }) {
|
function ObjRow({ passed, label, value }: { passed: boolean; label: ReactNode; value: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between py-3 border-b border-slate-100 last:border-0">
|
<div className="flex items-center justify-between py-3 border-b border-slate-100 last:border-0">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
@@ -378,7 +378,14 @@ export default function AccountPage() {
|
|||||||
<h2 className="text-slate-900 font-bold text-base mb-1">Objectives</h2>
|
<h2 className="text-slate-900 font-bold text-base mb-1">Objectives</h2>
|
||||||
<ObjRow
|
<ObjRow
|
||||||
passed={profitPassed}
|
passed={profitPassed}
|
||||||
label="Profit Target"
|
label={
|
||||||
|
<span className="inline-flex items-center gap-1.5">
|
||||||
|
Profit Target
|
||||||
|
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold bg-indigo-100 text-indigo-700">
|
||||||
|
Stage {account.stage}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
value={cfg ? `$${fmt(totalProfit)} of $${fmt(account.effectiveProfitTarget ?? cfg.profitTarget)}` : '—'}
|
value={cfg ? `$${fmt(totalProfit)} of $${fmt(account.effectiveProfitTarget ?? cfg.profitTarget)}` : '—'}
|
||||||
/>
|
/>
|
||||||
<ObjRow
|
<ObjRow
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export async function GET() {
|
|||||||
const isDead = autoLiqThreshold > 0 && cash.amount <= autoLiqThreshold;
|
const isDead = autoLiqThreshold > 0 && cash.amount <= autoLiqThreshold;
|
||||||
const allFundTxns = client.fundTransactions?.[acc.id] ?? [];
|
const allFundTxns = client.fundTransactions?.[acc.id] ?? [];
|
||||||
const priorProfit = client.priorProfit?.[acc.id] ?? 0;
|
const priorProfit = client.priorProfit?.[acc.id] ?? 0;
|
||||||
|
// Stage = 1 + number of withdrawals (negative fund transactions)
|
||||||
|
const stage = 1 + allFundTxns.filter((f) => f.amount < 0).length;
|
||||||
// Hide initial funding (amount === accountSize) from display
|
// Hide initial funding (amount === accountSize) from display
|
||||||
const displayFundTxns = cfg
|
const displayFundTxns = cfg
|
||||||
? allFundTxns.filter((f) => f.amount !== cfg.account_size)
|
? allFundTxns.filter((f) => f.amount !== cfg.account_size)
|
||||||
@@ -88,6 +90,7 @@ export async function GET() {
|
|||||||
autoLiqThreshold,
|
autoLiqThreshold,
|
||||||
totalProfit,
|
totalProfit,
|
||||||
targetHit,
|
targetHit,
|
||||||
|
stage,
|
||||||
dailyTarget,
|
dailyTarget,
|
||||||
effectiveProfitTarget,
|
effectiveProfitTarget,
|
||||||
dailyPnL,
|
dailyPnL,
|
||||||
|
|||||||
+6
-1
@@ -135,7 +135,12 @@ function AccountRow({ account, firm, hideDead, privacy }: { account: AccountStat
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-slate-600 tabular-nums">
|
<td className="px-4 py-3 text-slate-600 tabular-nums">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span>${(account.effectiveProfitTarget ?? cfg?.profitTarget ?? 0).toLocaleString(undefined, { maximumFractionDigits: 2 })}</span>
|
<div className="flex items-center gap-1.5">
|
||||||
|
<span>${(account.effectiveProfitTarget ?? cfg?.profitTarget ?? 0).toLocaleString(undefined, { maximumFractionDigits: 2 })}</span>
|
||||||
|
<span className="inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold bg-indigo-100 text-indigo-700">
|
||||||
|
Stage {account.stage}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{account.dailyTarget && (
|
{account.dailyTarget && (
|
||||||
<span className="text-xs text-slate-400">${fmt(account.dailyTarget.amount)} today</span>
|
<span className="text-xs text-slate-400">${fmt(account.dailyTarget.amount)} today</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export interface AccountState {
|
|||||||
totalProfit: number;
|
totalProfit: number;
|
||||||
/** True when today's realizedPnL has met or exceeded the computed daily target */
|
/** True when today's realizedPnL has met or exceeded the computed daily target */
|
||||||
targetHit: boolean;
|
targetHit: boolean;
|
||||||
|
/** Current stage = 1 + number of withdrawals (negative fund transactions). Stage 1 = fresh account. */
|
||||||
|
stage: number;
|
||||||
/** Next trading day's target, computed server-side. null when account is dead or config is missing. */
|
/** Next trading day's target, computed server-side. null when account is dead or config is missing. */
|
||||||
dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null;
|
dailyTarget: { amount: number; path: 'first_day' | 'normal_day' | 'reduced_day' } | null;
|
||||||
/** Actual profit target to hit — max(stage profit target, consistency realTarget = maxDay/consistency). */
|
/** Actual profit target to hit — max(stage profit target, consistency realTarget = maxDay/consistency). */
|
||||||
|
|||||||
Reference in New Issue
Block a user