refactor(ui): migrate visual strategy controls

This commit is contained in:
Saravana Achu Mac 2026-05-06 14:09:49 -07:00
parent a622326be6
commit bb4efc2b0d

View File

@ -19,8 +19,8 @@ import {
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { GripVertical, Plus, Trash2, Save, Play } from 'lucide-react';
import { Button } from '../ui/button';
import { Card, CardContent } from '../ui/card';
import { Button, IconButton, Input, Select } from '../ui/Primitives';
// ─── Types ────────────────────────────────────────────────────────────────────
export type Indicator = 'RSI' | 'MACD' | 'EMA_50' | 'EMA_200' | 'Price' | 'Volume';
@ -130,29 +130,35 @@ function RuleCard({
<span style={{ fontSize: 12, fontWeight: 600, color: '#374151' }}>IF</span>
{/* Indicator */}
<select value={rule.indicator} style={sel}
<Select
value={rule.indicator}
style={sel}
controlSize="sm"
variant="surface"
options={(Object.keys(INDICATOR_LABELS) as Indicator[]).map(k => ({ value: k, label: INDICATOR_LABELS[k] }))}
onChange={e => onChange(rule.id, {
indicator: e.target.value as Indicator,
value: INDICATOR_DEFAULTS[e.target.value as Indicator],
})}>
{(Object.keys(INDICATOR_LABELS) as Indicator[]).map(k => (
<option key={k} value={k}>{INDICATOR_LABELS[k]}</option>
))}
</select>
})}
/>
{/* Condition */}
<select value={rule.condition} style={sel}
onChange={e => onChange(rule.id, { condition: e.target.value as Condition })}>
{(Object.keys(CONDITION_LABELS) as Condition[]).map(k => (
<option key={k} value={k}>{CONDITION_LABELS[k]}</option>
))}
</select>
<Select
value={rule.condition}
style={sel}
controlSize="sm"
variant="surface"
options={(Object.keys(CONDITION_LABELS) as Condition[]).map(k => ({ value: k, label: CONDITION_LABELS[k] }))}
onChange={e => onChange(rule.id, { condition: e.target.value as Condition })}
/>
{/* Value */}
<input
<Input
type="number"
value={rule.value}
style={numInp}
controlSize="sm"
variant="surface"
onChange={e => onChange(rule.id, { value: parseFloat(e.target.value) || 0 })}
/>
@ -160,36 +166,51 @@ function RuleCard({
<span style={{ fontSize: 12, fontWeight: 600, color: '#374151' }}></span>
{/* Action */}
<select value={rule.action} style={{
<Select value={rule.action} style={{
...sel,
color: rule.action === 'BUY' ? '#16A34A' : '#DC2626',
fontWeight: 700,
background: rule.action === 'BUY' ? '#F0FDF4' : '#FEF2F2',
border: `1px solid ${rule.action === 'BUY' ? '#86EFAC' : '#FCA5A5'}`,
}}
onChange={e => onChange(rule.id, { action: e.target.value as TradeAction })}>
<option value="BUY">BUY</option>
<option value="SELL">SELL</option>
</select>
controlSize="sm"
variant="surface"
options={[
{ value: 'BUY', label: 'BUY' },
{ value: 'SELL', label: 'SELL' },
]}
onChange={e => onChange(rule.id, { action: e.target.value as TradeAction })}
/>
{/* Quantity */}
<input
<Input
type="number"
value={rule.quantity}
style={numInp}
min={1}
controlSize="sm"
variant="surface"
onChange={e => onChange(rule.id, { quantity: parseFloat(e.target.value) || 1 })}
/>
{/* Qty type */}
<select value={rule.quantityType} style={sel}
onChange={e => onChange(rule.id, { quantityType: e.target.value as QtyType })}>
<option value="shares">shares</option>
<option value="percent">% of capital</option>
</select>
<Select
value={rule.quantityType}
style={sel}
controlSize="sm"
variant="surface"
options={[
{ value: 'shares', label: 'shares' },
{ value: 'percent', label: '% of capital' },
]}
onChange={e => onChange(rule.id, { quantityType: e.target.value as QtyType })}
/>
{/* Delete */}
<button
<IconButton
type="button"
label="Remove rule"
icon={<Trash2 size={14} />}
onClick={() => onDelete(rule.id)}
style={{
marginLeft: 'auto', background: 'none', border: 'none',
@ -197,10 +218,7 @@ function RuleCard({
display: 'flex', alignItems: 'center',
flexShrink: 0,
}}
title="Remove rule"
>
<Trash2 size={14} />
</button>
/>
</div>
);
}
@ -295,9 +313,11 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) {
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
<div>
<div style={{ fontSize: 11, color: 'var(--muted-foreground)', fontWeight: 500, marginBottom: 3 }}>Strategy name</div>
<input
<Input
value={name}
onChange={e => setName(e.target.value)}
controlSize="sm"
variant="surface"
style={{
border: '1px solid var(--border)', borderRadius: 12,
padding: '7px 12px', fontSize: 14, fontWeight: 600,
@ -362,8 +382,11 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) {
</DndContext>
{/* Add rule */}
<button
<Button
type="button"
onClick={() => setRules(prev => [...prev, makeRule()])}
variant="outline"
size="sm"
style={{
display: 'flex', alignItems: 'center', gap: 7,
width: '100%', padding: '10px 0', border: '1px dashed var(--border-strong)',
@ -373,7 +396,7 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) {
}}
>
<Plus size={15} /> Add Rule
</button>
</Button>
{/* Rule summary */}
{rules.length > 0 && (