feat(simple): add manage existing holding flow
This commit is contained in:
parent
f51172518e
commit
1f03bb83cd
@ -667,6 +667,10 @@ export function SimpleView() {
|
|||||||
() => simpleHoldings.find((holding) => holding.symbol === normalizedSymbol) || null,
|
() => simpleHoldings.find((holding) => holding.symbol === normalizedSymbol) || null,
|
||||||
[simpleHoldings, normalizedSymbol],
|
[simpleHoldings, normalizedSymbol],
|
||||||
);
|
);
|
||||||
|
const availableSellHoldings = useMemo(
|
||||||
|
() => [...simpleHoldings].sort((left, right) => left.symbol.localeCompare(right.symbol)),
|
||||||
|
[simpleHoldings],
|
||||||
|
);
|
||||||
|
|
||||||
const supportedSymbols = useMemo(() => {
|
const supportedSymbols = useMemo(() => {
|
||||||
const fromState = Object.keys(symbolState || {}).map(normalizeKnownSymbol).filter(Boolean) as string[];
|
const fromState = Object.keys(symbolState || {}).map(normalizeKnownSymbol).filter(Boolean) as string[];
|
||||||
@ -751,6 +755,24 @@ export function SimpleView() {
|
|||||||
setDraft((prev) => ({ ...prev, [key]: value }));
|
setDraft((prev) => ({ ...prev, [key]: value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyHoldingToDraft(holding: SimpleHolding) {
|
||||||
|
setMarketPriceSource(null);
|
||||||
|
setDraft((prev) => ({
|
||||||
|
...prev,
|
||||||
|
side: 'sell',
|
||||||
|
symbol: holding.symbol,
|
||||||
|
quantity: String(holding.size),
|
||||||
|
currentMarketPrice: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (draft.side !== 'sell') return;
|
||||||
|
if (matchingHolding) return;
|
||||||
|
if (availableSellHoldings.length === 0) return;
|
||||||
|
applyHoldingToDraft(availableSellHoldings[0]);
|
||||||
|
}, [draft.side, matchingHolding, availableSellHoldings]);
|
||||||
|
|
||||||
async function copyIdentifier(kind: 'trade' | 'order', value: string | null | undefined) {
|
async function copyIdentifier(kind: 'trade' | 'order', value: string | null | undefined) {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
try {
|
try {
|
||||||
@ -950,7 +972,7 @@ export function SimpleView() {
|
|||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="uppercase">{editingSetupId ? 'Edit setup' : 'New setup'}</CardTitle>
|
<CardTitle className="uppercase">{editingSetupId ? 'Edit setup' : 'New setup'}</CardTitle>
|
||||||
<CardDescription>Saved trigger workflow, not an immediate broker order</CardDescription>
|
<CardDescription>Build a short-term buy plan or attach a managed profit exit to an existing holding.</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@ -974,6 +996,74 @@ export function SimpleView() {
|
|||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||||
|
<div className="grid gap-3 md:grid-cols-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setError(null);
|
||||||
|
setMessage(null);
|
||||||
|
setDraft((prev) => ({ ...prev, side: 'buy' }));
|
||||||
|
}}
|
||||||
|
className={`rounded-[1.25rem] border px-4 py-4 text-left transition ${
|
||||||
|
draft.side === 'buy'
|
||||||
|
? 'border-[var(--primary)] bg-[var(--accent-soft)]'
|
||||||
|
: 'border-[var(--border)] bg-[var(--card-elevated)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="text-[11px] font-black uppercase tracking-[0.24em] text-[var(--muted-foreground)]">Create plan</div>
|
||||||
|
<div className="mt-1 text-sm font-semibold text-[var(--foreground)]">New short-term buy plan</div>
|
||||||
|
<div className="mt-1 text-sm text-[var(--muted-foreground)]">Arm a dip-buy trigger and let the app manage the profit exit after fill.</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setError(null);
|
||||||
|
setMessage(null);
|
||||||
|
if (availableSellHoldings.length > 0) {
|
||||||
|
applyHoldingToDraft(availableSellHoldings[0]);
|
||||||
|
} else {
|
||||||
|
setDraft((prev) => ({ ...prev, side: 'sell' }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={`rounded-[1.25rem] border px-4 py-4 text-left transition ${
|
||||||
|
draft.side === 'sell'
|
||||||
|
? 'border-[var(--primary)] bg-[var(--accent-soft)]'
|
||||||
|
: 'border-[var(--border)] bg-[var(--card-elevated)]'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="text-[11px] font-black uppercase tracking-[0.24em] text-[var(--muted-foreground)]">Manage holding</div>
|
||||||
|
<div className="mt-1 text-sm font-semibold text-[var(--foreground)]">Attach an exit plan</div>
|
||||||
|
<div className="mt-1 text-sm text-[var(--muted-foreground)]">Choose an existing holding and place it back under managed profit-taking.</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{draft.side === 'sell' && (
|
||||||
|
<label className="space-y-2">
|
||||||
|
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Existing holding</span>
|
||||||
|
<Select
|
||||||
|
value={matchingHolding?.symbol || normalizedSymbol}
|
||||||
|
onChange={(e) => {
|
||||||
|
const selected = availableSellHoldings.find((holding) => holding.symbol === e.target.value);
|
||||||
|
if (selected) applyHoldingToDraft(selected);
|
||||||
|
}}
|
||||||
|
disabled={availableSellHoldings.length === 0}
|
||||||
|
>
|
||||||
|
{availableSellHoldings.length === 0 ? (
|
||||||
|
<option value="">No eligible holdings available</option>
|
||||||
|
) : (
|
||||||
|
availableSellHoldings.map((holding) => (
|
||||||
|
<option key={`${holding.symbol}:${holding.tradeId || 'holding'}`} value={holding.symbol}>
|
||||||
|
{holding.symbol} · {holding.size} @ {holding.entryPrice.toFixed(4)}
|
||||||
|
</option>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
<span className="block text-[11px] text-[var(--muted-foreground)]">
|
||||||
|
Trade Plans can manage an existing filled holding by attaching a profit exit target to it.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<label className="space-y-2">
|
<label className="space-y-2">
|
||||||
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Symbol</span>
|
<span className="text-[11px] font-black uppercase tracking-[0.24em] text-zinc-700">Symbol</span>
|
||||||
@ -1037,7 +1127,7 @@ export function SimpleView() {
|
|||||||
onChange={(e) => updateDraft('side', e.target.value as SimpleSide)}
|
onChange={(e) => updateDraft('side', e.target.value as SimpleSide)}
|
||||||
>
|
>
|
||||||
<option value="buy">Buy the dip + profit exit</option>
|
<option value="buy">Buy the dip + profit exit</option>
|
||||||
<option value="sell">Sell existing Simple holding at profit</option>
|
<option value="sell">Manage existing holding at profit</option>
|
||||||
</Select>
|
</Select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user