diff --git a/app/routes/settings.tsx b/app/routes/settings.tsx index de8e387..53eacb6 100644 --- a/app/routes/settings.tsx +++ b/app/routes/settings.tsx @@ -1,46 +1,129 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState } from "react"; +import Navbar from "../components/Navbar"; +import SettingsSidebar, { type SettingsSection } from "../components/SettingsSidebar"; +import LlmSettings from "../components/LlmSettings"; +import TradingSettings from "../components/TradingSettings"; +import StockTable from "../components/StockTable"; +import SystemSettings from "../components/SystemSettings"; + +interface Stock { + id: string; + ticker: string; + notes: string | null; + lastDecision: string | null; + lastJobId: string | null; + createdAt: string; + updatedAt: string; +} export default function SettingsPage() { - const [items, setItems] = useState>([]); + const [activeSection, setActiveSection] = useState("llm"); + const [settings, setSettings] = useState>({}); + const [stocks, setStocks] = useState([]); + const [loading, setLoading] = useState(true); + const [saveError, setSaveError] = useState(null); + const [alpacaMode, setAlpacaMode] = useState(null); + useEffect(() => { - fetch('/api/admin/settings') - .then(r => r.json()) - .then(j => setItems(j)); + Promise.all([ + fetch("/api/admin/settings").then((r) => r.json()), + fetch("/api/stocks").then((r) => r.json()), + fetch("/api/alpaca/account").then((r) => r.ok ? r.json() : null), + ]).then(([settingsData, stocksData, accountData]) => { + const settingsMap: Record = {}; + if (Array.isArray(settingsData)) { + settingsData.forEach((s: { key: string; value: any }) => { + settingsMap[s.key] = s.value; + }); + } + setSettings(settingsMap); + if (Array.isArray(stocksData)) setStocks(stocksData); + if (accountData?.trading?.paper !== undefined) { + setAlpacaMode(accountData.trading.paper ? "paper" : "live"); + } + setLoading(false); + }).catch((err) => { + console.error("Failed to load settings:", err); + setLoading(false); + }); }, []); - async function save(key: string, value: any) { - await fetch(`/api/admin/settings/${encodeURIComponent(key)}`, { - method: 'PUT', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ value }), - }); - setItems(s => s.map(i => (i.key === key ? { ...i, value } : i))); + const saveSetting = async (key: string, value: any) => { + setSaveError(null); + const prevValue = settings[key]; + setSettings((s) => ({ ...s, [key]: value })); + try { + const res = await fetch(`/api/admin/settings/${encodeURIComponent(key)}`, { + method: "PUT", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ value }), + }); + if (!res.ok) { + throw new Error(`Failed to save ${key}`); + } + } catch (err) { + setSettings((s) => ({ ...s, [key]: prevValue })); + setSaveError(err instanceof Error ? err.message : "Save failed"); + } + }; + + const saveStockNotes = async (ticker: string, notes: string) => { + setSaveError(null); + const prevStocks = [...stocks]; + setStocks((s) => s.map((st) => (st.ticker === ticker ? { ...st, notes } : st))); + try { + const fd = new FormData(); + fd.append("ticker", ticker); + fd.append("notes", notes); + const res = await fetch("/api/stocks", { method: "POST", body: fd }); + if (!res.ok) { + throw new Error("Failed to save notes"); + } + } catch (err) { + setStocks(prevStocks); + setSaveError(err instanceof Error ? err.message : "Save failed"); + } + }; + + if (loading) { + return ( +
+ +
+
Loading settings...
+
+
+ ); } + const renderSection = () => { + switch (activeSection) { + case "llm": + return ; + case "trading": + return ; + case "stocks": + return ; + case "system": + return ; + default: + return null; + } + }; + return ( -
-

Settings

-
    - {items.map(it => ( -
  • -
    -
    {it.key}
    -