diff --git a/web/src/tabs/EntriesTab.tsx b/web/src/tabs/EntriesTab.tsx index e5426e8..4b9ea60 100644 --- a/web/src/tabs/EntriesTab.tsx +++ b/web/src/tabs/EntriesTab.tsx @@ -5,24 +5,25 @@ import { Pencil, Trash2, Copy as CopyIcon, Search, Plus, Filter, LayoutGrid, Clo import type { BotState } from '../hooks/useWebSocket'; import { DEFAULT_BOT_STATE } from '../hooks/useWebSocket'; import { createManualEntry, deleteManualEntry, fetchManualEntries, type ManualEntryPayload } from '../lib/manualEntriesApi'; - +import { Button } from '../components/ui/Primitives'; + interface Entry { stock_instance_id: string; symbol: string; - active: boolean; - user_id: string; - buy_price?: string; - sell_price?: string; - buy_time?: string; - sell_time?: string; - quantity?: string; - filled_quantity?: string; - notes?: string; - status: string; - is_crypto: boolean; - is_real_trade: boolean; - label?: string; - entry_price?: string; + active: boolean; + user_id: string; + buy_price?: string; + sell_price?: string; + buy_time?: string; + sell_time?: string; + quantity?: string; + filled_quantity?: string; + notes?: string; + status: string; + is_crypto: boolean; + is_real_trade: boolean; + label?: string; + entry_price?: string; gain_threshold_for_sell?: string; drop_threshold_for_buy?: string; workflow_type?: string; @@ -36,80 +37,80 @@ export const filterEntriesByTab = (entries: Entry[], activeTab: string) => return false; } switch (activeTab) { - case 'paperActive': - return !entry.is_real_trade && entry.active && entry.status !== 'sellCompleted'; - case 'paperCompleted': - return !entry.is_real_trade && entry.status === 'sellCompleted'; - case 'realActive': - return entry.is_real_trade && entry.active && entry.status !== 'sellCompleted'; - case 'realCompleted': - return entry.is_real_trade && entry.status === 'sellCompleted'; - case 'inactive': - return !entry.active; - default: - return false; - } - }); - -export const buildClonedEntryPayload = (entry: Entry, generatedId: string) => { - const rest = { ...entry } as Record; - delete rest.stock_instance_id; - delete rest.id; - delete rest.created_at; - delete rest.updated_at; - - return { - ...rest, - stock_instance_id: generatedId, - active: false, - status: 'active' - }; -}; - -interface EntriesTabProps { - botState?: BotState; -} - -const ENTRY_STATE_MAP = [ - { label: 'LOCKED', test: (entry: Entry) => entry.status?.toLowerCase().includes('lock') || entry.active }, - { label: 'BLOCKED', test: (entry: Entry) => ['blocked', 'cooldown', 'waiting'].some(term => entry.status?.toLowerCase().includes(term)) }, - { label: 'ORPHAN', test: (entry: Entry) => ['orphan', 'stale', 'reconcile'].some(term => entry.status?.toLowerCase().includes(term)) }, -]; - -const deriveEntryState = (entry: Entry, botState: BotState): string => { - const orderMatch = botState.orders.find(o => o.symbol === entry.symbol); - const status = (entry.status || '').toLowerCase(); - const orderStatus = (orderMatch?.status || '').toLowerCase(); - - if (ENTRY_STATE_MAP[0].test(entry)) return 'LOCKED'; - if (ENTRY_STATE_MAP[1].test(entry)) return 'BLOCKED'; - if (orderStatus.includes('pending') || orderStatus.includes('submitted') || status.includes('submitted')) return 'SUBMITTED'; - if (orderStatus.includes('filled') || orderStatus.includes('confirmed') || status.includes('filled') || status.includes('confirmed')) return 'CONFIRMED'; - if (ENTRY_STATE_MAP[2].test(entry)) return 'ORPHAN'; - return 'Unknown'; -}; - -const NAV_TABS = [ - { id: "paperActive", label: "Paper Active", icon: }, - { id: "realActive", label: "Real Execution", icon: }, - { id: "paperCompleted", label: "Paper Archive", icon: }, - { id: "realCompleted", label: "Real Archive", icon: }, - { id: "inactive", label: "Suspended", icon: } -] as const; - -const tabClass = (isActive: boolean) => - `px-6 py-3 rounded-[1.5rem] text-[10px] font-black uppercase tracking-widest transition-all flex items-center gap-3 whitespace-nowrap ${isActive ? "bg-white text-black shadow-xl" : "text-gray-500 hover:text-gray-300"}`; - -export const EntriesTab = ({ botState = DEFAULT_BOT_STATE }: EntriesTabProps) => { - const { user } = useAuth(); - const [entries, setEntries] = useState([]); - const [editingEntry, setEditingEntry] = useState(null); - const [activeTab, setActiveTab] = useState("paperActive"); - const [isAdding, setIsAdding] = useState(false); - - // Filter Logic - const filteredEntries = filterEntriesByTab(entries, activeTab); - + case 'paperActive': + return !entry.is_real_trade && entry.active && entry.status !== 'sellCompleted'; + case 'paperCompleted': + return !entry.is_real_trade && entry.status === 'sellCompleted'; + case 'realActive': + return entry.is_real_trade && entry.active && entry.status !== 'sellCompleted'; + case 'realCompleted': + return entry.is_real_trade && entry.status === 'sellCompleted'; + case 'inactive': + return !entry.active; + default: + return false; + } + }); + +export const buildClonedEntryPayload = (entry: Entry, generatedId: string) => { + const rest = { ...entry } as Record; + delete rest.stock_instance_id; + delete rest.id; + delete rest.created_at; + delete rest.updated_at; + + return { + ...rest, + stock_instance_id: generatedId, + active: false, + status: 'active' + }; +}; + +interface EntriesTabProps { + botState?: BotState; +} + +const ENTRY_STATE_MAP = [ + { label: 'LOCKED', test: (entry: Entry) => entry.status?.toLowerCase().includes('lock') || entry.active }, + { label: 'BLOCKED', test: (entry: Entry) => ['blocked', 'cooldown', 'waiting'].some(term => entry.status?.toLowerCase().includes(term)) }, + { label: 'ORPHAN', test: (entry: Entry) => ['orphan', 'stale', 'reconcile'].some(term => entry.status?.toLowerCase().includes(term)) }, +]; + +const deriveEntryState = (entry: Entry, botState: BotState): string => { + const orderMatch = botState.orders.find(o => o.symbol === entry.symbol); + const status = (entry.status || '').toLowerCase(); + const orderStatus = (orderMatch?.status || '').toLowerCase(); + + if (ENTRY_STATE_MAP[0].test(entry)) return 'LOCKED'; + if (ENTRY_STATE_MAP[1].test(entry)) return 'BLOCKED'; + if (orderStatus.includes('pending') || orderStatus.includes('submitted') || status.includes('submitted')) return 'SUBMITTED'; + if (orderStatus.includes('filled') || orderStatus.includes('confirmed') || status.includes('filled') || status.includes('confirmed')) return 'CONFIRMED'; + if (ENTRY_STATE_MAP[2].test(entry)) return 'ORPHAN'; + return 'Unknown'; +}; + +const NAV_TABS = [ + { id: "paperActive", label: "Paper Active", icon: }, + { id: "realActive", label: "Real Execution", icon: }, + { id: "paperCompleted", label: "Paper Archive", icon: }, + { id: "realCompleted", label: "Real Archive", icon: }, + { id: "inactive", label: "Suspended", icon: } +] as const; + +const tabClass = (isActive: boolean) => + `px-6 py-3 rounded-[1.5rem] text-[10px] font-black uppercase tracking-widest transition-all flex items-center gap-3 whitespace-nowrap ${isActive ? "bg-white text-black shadow-xl" : "text-gray-500 hover:text-gray-300"}`; + +export const EntriesTab = ({ botState = DEFAULT_BOT_STATE }: EntriesTabProps) => { + const { user } = useAuth(); + const [entries, setEntries] = useState([]); + const [editingEntry, setEditingEntry] = useState(null); + const [activeTab, setActiveTab] = useState("paperActive"); + const [isAdding, setIsAdding] = useState(false); + + // Filter Logic + const filteredEntries = filterEntriesByTab(entries, activeTab); + const fetchEntries = async () => { if (!user) return; try { @@ -118,13 +119,13 @@ export const EntriesTab = ({ botState = DEFAULT_BOT_STATE }: EntriesTabProps) => } catch (err: unknown) { const message = err instanceof Error ? err.message : String(err); console.error("Error fetching entries:", message); - } - }; - - useEffect(() => { - fetchEntries(); - }, [user]); - + } + }; + + useEffect(() => { + fetchEntries(); + }, [user]); + const handleDelete = async (id: string) => { if (!confirm('⚠️ CRITICAL: Permanently delete this entry from neural watchlist?')) return; await deleteManualEntry(id); @@ -135,159 +136,163 @@ export const EntriesTab = ({ botState = DEFAULT_BOT_STATE }: EntriesTabProps) => await createManualEntry(buildClonedEntryPayload(entry, crypto.randomUUID()) as unknown as ManualEntryPayload); fetchEntries(); }; - - const entryCards = filteredEntries.map((entry) => { - const entryState = deriveEntryState(entry, botState); - return ( -
-
- - {/* Card Top */} -
-
-

{entry.symbol}

-
- - {entry.is_real_trade ? 'REAL-UNIT' : 'PAPER-VOID'} - - {entry.label && ( - - {entry.label} - - )} -
-
-
- - - -
-
- - {/* Metrics Grid */} -
-
- Entry Price - ${entry.buy_price || '0.00'} -
-
- Target Exit - ${entry.sell_price || '---'} -
-
- Quantity/Lot - {entry.quantity} UNITS -
-
- Status - - {entry.active ? 'SCANNING' : 'INACTIVE'} - -
-
- -
- Entry State - - {entryState} - -
- - {/* Entry Notes/Context */} -
- Neural Context -

- {entry.notes || 'No manual notes injection provided for this asset cluster.'} -

-
- - {/* ID Context */} -
-
-
- ID -
- {entry.stock_instance_id} -
- -
-
-
- ); - }); - - const navTabs = NAV_TABS; - - return ( -
- - {/* 1. HEADER & GLOBAL ACTIONS */} -
-
-
- -
-
-

Watchlist & Entries

-

Neural Opportunity Cluster

-
-
- - -
- - {/* 2. FORM DRAWER (IF ADDING/EDITING) */} - {(isAdding || editingEntry) && ( -
-
-

- - {editingEntry ? `Update Identity: ${editingEntry.symbol}` : 'New Opportunity Initialization'} -

- -
-
- { fetchEntries(); setEditingEntry(null); setIsAdding(false); }} - initialData={editingEntry} - /> -
-
- )} - - {/* 3. NAVIGATION CLUSTERS */} + + const entryCards = filteredEntries.map((entry) => { + const entryState = deriveEntryState(entry, botState); + return ( +
+
+ + {/* Card Top */} +
+
+

{entry.symbol}

+
+ + {entry.is_real_trade ? 'REAL-UNIT' : 'PAPER-VOID'} + + {entry.label && ( + + {entry.label} + + )} +
+
+
+ + + +
+
+ + {/* Metrics Grid */} +
+
+ Entry Price + ${entry.buy_price || '0.00'} +
+
+ Target Exit + ${entry.sell_price || '---'} +
+
+ Quantity/Lot + {entry.quantity} UNITS +
+
+ Status + + {entry.active ? 'SCANNING' : 'INACTIVE'} + +
+
+ +
+ Entry State + + {entryState} + +
+ + {/* Entry Notes/Context */} +
+ Neural Context +

+ {entry.notes || 'No manual notes injection provided for this asset cluster.'} +

+
+ + {/* ID Context */} +
+
+
+ ID +
+ {entry.stock_instance_id} +
+ +
+
+
+ ); + }); + + const navTabs = NAV_TABS; + + return ( +
+ + {/* 1. HEADER & GLOBAL ACTIONS */} +
+
+
+ +
+
+

Watchlist & Entries

+

Neural Opportunity Cluster

+
+
+ + +
+ + {/* 2. FORM DRAWER (IF ADDING/EDITING) */} + {(isAdding || editingEntry) && ( +
+
+

+ + {editingEntry ? `Update Identity: ${editingEntry.symbol}` : 'New Opportunity Initialization'} +

+ +
+
+ { fetchEntries(); setEditingEntry(null); setIsAdding(false); }} + initialData={editingEntry} + /> +
+
+ )} + + {/* 3. NAVIGATION CLUSTERS */}
- {navTabs.map((tab) => ( - - ))} -
- {/* 4. CARDS GRID */} -
- {entryCards} - {filteredEntries.length === 0 && ( -
- -

Cluster Context Null

-
- )} -
-
- ); -}; - -const X = ({ size }: { size: number }) => ( - + {navTabs.map((tab) => ( + + ))} +
+ {/* 4. CARDS GRID */} +
+ {entryCards} + {filteredEntries.length === 0 && ( +
+ +

Cluster Context Null

+
+ )} +
+ + ); +}; + +const X = ({ size }: { size: number }) => ( + ); diff --git a/web/src/tabs/SettingsTab.tsx b/web/src/tabs/SettingsTab.tsx index 2db7890..d320cdb 100644 --- a/web/src/tabs/SettingsTab.tsx +++ b/web/src/tabs/SettingsTab.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useAuth } from '../components/AuthContext'; import type { BotState } from '../hooks/useWebSocket'; import { updateCurrentUserProfile } from '../lib/profileApi'; +import { Button, Input } from '../components/ui/Primitives'; interface SettingsTabProps { botState: BotState; @@ -129,13 +130,13 @@ export const SettingsTab = ({ botState }: SettingsTabProps) => {

User Configuration

{!editing ? ( - + ) : (
- - + +
)}
@@ -143,7 +144,7 @@ export const SettingsTab = ({ botState }: SettingsTabProps) => {
- {
- {
- {
- { {/* Threshold Settings */}
- {
- {
- {
- {
- {
- {
- {
-