refactor(ui): redesign manual entry form

This commit is contained in:
Saravana Achu Mac 2026-05-07 13:25:54 -07:00
parent 2a23f88334
commit f598084f6a
2 changed files with 112 additions and 206 deletions

View File

@ -5,17 +5,15 @@ import { tradingRuntime } from '../lib/runtime';
import { createManualEntry, updateManualEntry } from '../lib/manualEntriesApi'; import { createManualEntry, updateManualEntry } from '../lib/manualEntriesApi';
import { getPlatformAccessToken } from '../lib/authSession'; import { getPlatformAccessToken } from '../lib/authSession';
import { createRequestId } from '../../../shared/request-id.js'; import { createRequestId } from '../../../shared/request-id.js';
import { Button } from './ui/button'; import { Button, Checkbox, Input } from './ui/Primitives';
import { Input } from './ui/input';
interface EntryFormProps { interface EntryFormProps {
onSuccess: () => void; onSuccess: () => void;
initialData?: any; initialData?: any;
} }
const tableInputClass = 'h-9 rounded border-[var(--border)] bg-[var(--input)] px-2 py-1 text-sm'; const numericInputClass = 'font-mono text-right';
const numericInputClass = `${tableInputClass} font-mono text-right`; const checkboxLabelClass = 'flex min-h-10 cursor-pointer items-center gap-2 rounded-[var(--bl-radius-control)] border border-[var(--border)] bg-[var(--card-elevated)] px-3 text-sm text-[var(--foreground)] transition hover:border-[var(--primary)]';
const checkboxLabelClass = 'flex cursor-pointer items-center text-[10px] text-[var(--muted-foreground)] hover:text-[var(--foreground)]';
export function EntryForm({ onSuccess, initialData }: EntryFormProps) { export function EntryForm({ onSuccess, initialData }: EntryFormProps) {
const { user } = useAuth(); const { user } = useAuth();
@ -180,210 +178,93 @@ export function EntryForm({ onSuccess, initialData }: EntryFormProps) {
</div> </div>
)} )}
<form onSubmit={handleSubmit} className="table-container"> <form onSubmit={handleSubmit} className="space-y-5 rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card)] p-5 shadow-[var(--bl-shadow-card)]">
<table className="pro-table"> <section className="rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5">
<thead> <div className="mb-4">
<tr> <div className="text-sm font-bold text-[var(--foreground)]">Instrument</div>
<th style={{ width: '15%' }}>Symbol</th> <p className="mt-1 text-sm leading-6 text-[var(--muted-foreground)]">Name the opportunity and choose whether it is a watch item, trade journal entry, or crypto setup.</p>
<th style={{ width: '10%' }}>Tag</th> </div>
<th style={{ width: '15%' }}>Options</th> <div className="grid gap-4 md:grid-cols-2">
<th style={{ width: '12%' }} className="text-right">Buy Price</th> <label className="ux-field-stack">
<th style={{ width: '10%' }} className="text-right">Qty</th> <span className="ux-field-label">Symbol</span>
<th style={{ width: '10%' }} className="text-right">Stop Loss</th> <Input type="text" name="symbol" required value={formData.symbol} onChange={handleChange} placeholder="BTC/USD" />
<th style={{ width: '10%' }} className="text-right">Take Profit</th> </label>
<th style={{ width: '12%' }}>Buy Time</th> <label className="ux-field-stack">
<th style={{ width: '10%' }} className="text-right">Exit Price</th> <span className="ux-field-label">Tag</span>
<th style={{ width: '11%' }}>Actions</th> <Input type="text" name="label" value={formData.label} onChange={handleChange} placeholder="Swing" />
</tr> </label>
</thead> </div>
<tbody> <div className="mt-4 grid gap-3 md:grid-cols-3">
<tr> <div className={checkboxLabelClass} title="Places actual order on Bot">
{/* Symbol */} <Checkbox name="execute_order" checked={formData.execute_order} onChange={handleChange} label="Execute" />
<td> </div>
<Input <div className={checkboxLabelClass} title="Marks as Active Trade in Journal">
type="text" <Checkbox name="is_real_trade" checked={formData.is_real_trade} onChange={handleChange} label="Trade" />
name="symbol" </div>
required <div className={checkboxLabelClass}>
value={formData.symbol} <Checkbox name="is_crypto" checked={formData.is_crypto} onChange={handleChange} label="Crypto" />
onChange={handleChange} </div>
placeholder="BTC/USD" </div>
className={tableInputClass} </section>
/>
</td>
{/* Label */} <section className="rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5">
<td> <div className="mb-4">
<Input <div className="text-sm font-bold text-[var(--foreground)]">Pricing and risk</div>
type="text" <p className="mt-1 text-sm leading-6 text-[var(--muted-foreground)]">Capture the planned entry, size, stop loss, take profit, and optional exit price.</p>
name="label" </div>
value={formData.label} <div className="grid gap-4 md:grid-cols-3">
onChange={handleChange} <label className="ux-field-stack">
placeholder="Swing" <span className="ux-field-label">Buy price</span>
className={tableInputClass} <Input type="number" name="buy_price" step="any" value={formData.buy_price} onChange={handleChange} className={`${numericInputClass} text-[var(--bl-success)]`} placeholder="0.00" />
/> </label>
</td> <label className="ux-field-stack">
<span className="ux-field-label">Quantity</span>
<Input type="number" name="quantity" step="any" value={formData.quantity} onChange={handleChange} className={numericInputClass} placeholder="0" />
</label>
<label className="ux-field-stack">
<span className="ux-field-label">Buy time</span>
<Input type="datetime-local" name="buy_time" value={formData.buy_time ? new Date(formData.buy_time).toISOString().slice(0, 16) : ''} onChange={(e) => setFormData(prev => ({ ...prev, buy_time: new Date(e.target.value).toISOString() }))} className="font-mono" />
</label>
<label className="ux-field-stack">
<span className="ux-field-label">Stop loss</span>
<Input type="number" name="drop_threshold_for_buy" step="any" value={formData.drop_threshold_for_buy} onChange={handleChange} className={`${numericInputClass} text-[var(--bl-danger)]`} placeholder="SL" />
</label>
<label className="ux-field-stack">
<span className="ux-field-label">Take profit</span>
<Input type="number" name="gain_threshold_for_sell" step="any" value={formData.gain_threshold_for_sell} onChange={handleChange} className={`${numericInputClass} text-[var(--bl-info)]`} placeholder="TP" />
</label>
<label className="ux-field-stack">
<span className="ux-field-label">Exit price</span>
<Input type="number" name="sell_price" step="any" value={formData.sell_price} onChange={handleChange} className={`${numericInputClass} text-[var(--bl-info-strong)]`} placeholder="Exit" />
{formData.sell_price && formData.buy_price && (
<span className={`text-xs font-bold ${(Number(formData.sell_price) - Number(formData.buy_price)) * Number(formData.quantity || 0) >= 0 ? 'text-[var(--bl-success)]' : 'text-[var(--bl-danger)]'}`}>
Estimated P/L ${((Number(formData.sell_price) - Number(formData.buy_price)) * Number(formData.quantity || 1)).toFixed(2)}
</span>
)}
</label>
</div>
</section>
{/* Options (Real/Crypto) */} <section className="rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-5">
<td> <label className="ux-field-stack">
<div className="flex flex-col gap-1"> <span className="ux-field-label">Strategy notes</span>
<label className="flex cursor-pointer items-center text-xs text-amber-600 hover:text-amber-500 dark:text-amber-400 dark:hover:text-amber-300" title="Places actual order on Bot"> <Input type="text" name="notes" value={formData.notes} onChange={handleChange} placeholder="Add strategy notes here..." />
<input </label>
type="checkbox" </section>
name="execute_order"
checked={formData.execute_order}
onChange={handleChange}
className="mr-1.5 accent-yellow-500"
/>
Execute
</label>
<div className="flex items-center space-x-2">
<label className={checkboxLabelClass} title="Marks as Active Trade in Journal">
<input
type="checkbox"
name="is_real_trade"
checked={formData.is_real_trade}
onChange={handleChange}
className="mr-1 accent-blue-500 scale-75"
/>
Trade
</label>
<label className={checkboxLabelClass}>
<input
type="checkbox"
name="is_crypto"
checked={formData.is_crypto}
onChange={handleChange}
className="mr-1 accent-purple-500 scale-75"
/>
Crypto
</label>
</div>
</div>
</td>
{/* Buy Price */} <div className="flex flex-col gap-3 rounded-[var(--bl-radius-card)] border border-[var(--border)] bg-[var(--card-elevated)] p-4 md:flex-row md:items-center md:justify-between">
<td> <p className="text-sm leading-6 text-[var(--muted-foreground)]">Review the symbol, sizing, and risk fields before saving this manual entry.</p>
<Input <div className="flex items-center justify-end gap-2">
type="number" {initialData && (
name="buy_price" <Button type="button" onClick={onSuccess} variant="ghost">
step="any" Cancel
value={formData.buy_price} </Button>
onChange={handleChange} )}
className={`${numericInputClass} text-emerald-600 dark:text-emerald-400`} <Button type="submit">
placeholder="0.00" {initialData ? 'Update' : 'Add'}
/> </Button>
</td> </div>
</div>
{/* Qty */}
<td>
<Input
type="number"
name="quantity"
step="any"
value={formData.quantity}
onChange={handleChange}
className={numericInputClass}
placeholder="0"
/>
</td>
{/* SL */}
<td>
<Input
type="number"
name="drop_threshold_for_buy"
step="any"
value={formData.drop_threshold_for_buy}
onChange={handleChange}
className={`${numericInputClass} text-red-600 dark:text-red-400`}
placeholder="SL"
/>
</td>
{/* TP */}
<td>
<Input
type="number"
name="gain_threshold_for_sell"
step="any"
value={formData.gain_threshold_for_sell}
onChange={handleChange}
className={`${numericInputClass} text-blue-600 dark:text-blue-400`}
placeholder="TP"
/>
</td>
{/* Time */}
<td>
<Input
type="datetime-local"
name="buy_time"
value={formData.buy_time ? new Date(formData.buy_time).toISOString().slice(0, 16) : ''}
onChange={(e) => setFormData(prev => ({ ...prev, buy_time: new Date(e.target.value).toISOString() }))}
className="h-9 rounded border-[var(--border)] bg-[var(--input)] px-2 py-1 font-mono text-xs"
/>
</td>
{/* Sell Price (Exit) */}
<td className="relative">
<Input
type="number"
name="sell_price"
step="any"
value={formData.sell_price}
onChange={handleChange}
className={`${numericInputClass} text-purple-600 dark:text-purple-400`}
placeholder="Exit"
/>
{formData.sell_price && formData.buy_price && (
<div className={`absolute top-0 right-0 -mt-3 text-[9px] font-bold ${(Number(formData.sell_price) - Number(formData.buy_price)) * Number(formData.quantity || 0) >= 0
? 'text-green-500'
: 'text-red-500'
}`}>
${((Number(formData.sell_price) - Number(formData.buy_price)) * Number(formData.quantity || 1)).toFixed(2)}
</div>
)}
</td>
{/* Actions */}
<td>
<div className="flex items-center justify-end gap-2">
{initialData && (
<Button
type="button"
onClick={onSuccess}
variant="ghost"
size="sm"
className="h-8 text-xs underline"
>
Cancel
</Button>
)}
<Button
type="submit"
size="sm"
className="h-8 text-xs"
>
{initialData ? 'Update' : 'Add'}
</Button>
</div>
</td>
</tr>
{/* Notes Row */}
<tr>
<td colSpan={10} className="border-t border-[var(--border)] pt-2">
<Input
type="text"
name="notes"
value={formData.notes}
onChange={handleChange}
placeholder="Add strategy notes here..."
className="h-9 border-transparent bg-transparent px-0 text-xs italic shadow-none focus:border-transparent focus:ring-0"
/>
</td>
</tr>
</tbody>
</table>
</form> </form>
</div> </div>
); );

View File

@ -49,6 +49,10 @@ export interface BadgeProps extends Omit<CommonBadgeProps, 'variant'> {
variant?: ProductBadgeVariant; variant?: ProductBadgeVariant;
} }
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> {
label?: React.ReactNode;
}
export type ProductStatus = export type ProductStatus =
| 'active' | 'active'
| 'approved' | 'approved'
@ -208,6 +212,27 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
Textarea.displayName = 'Textarea'; Textarea.displayName = 'Textarea';
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
({ className, label, id, ...props }, ref) => {
const generatedId = React.useId();
const checkboxId = id ?? (label ? `checkbox-${generatedId}` : undefined);
return (
<label className="inline-flex items-center gap-2 text-sm text-[var(--foreground)]">
<input
ref={ref}
id={checkboxId}
type="checkbox"
className={cn('h-4 w-4 rounded border-[var(--border)] bg-[var(--card)] text-[var(--primary)] focus-visible:ring-2 focus-visible:ring-[var(--ring-soft)]', className)}
{...props}
/>
{label}
</label>
);
},
);
Checkbox.displayName = 'Checkbox';
export function Badge({ variant = 'neutral', ...props }: BadgeProps) { export function Badge({ variant = 'neutral', ...props }: BadgeProps) {
return <CommonBadge variant={badgeVariantFor(variant)} {...props} />; return <CommonBadge variant={badgeVariantFor(variant)} {...props} />;
} }