feat: new cockpit

This commit is contained in:
OpenSquared
2026-07-14 11:21:43 +02:00
parent 9778c74ae3
commit ad07c8d886
32 changed files with 871 additions and 551 deletions

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useSources, useUpdateSources, useUpdateApiKeys, useConfig, useAiStatus, useAnalysisConfig, useSaveAnalysisConfig, useCycleStatus, useUpdateCycleConfig, useTriggerCycle, useRiskProfiles, useUpsertProfile, useDeleteProfile, useExitDefaults, useSaveExitDefaults, useOptionsGate, useSaveOptionsGate, useTechIndicatorsConfig, useSaveTechIndicatorsConfig } from '../hooks/useApi'
import { Settings, Key, Globe, CheckCircle, XCircle, AlertCircle, Save, Eye, EyeOff, Brain, SlidersHorizontal, RefreshCw, Zap, Plus, Trash2, Pencil, X, Lock, Gauge, DollarSign, TrendingUp, ShieldAlert, DatabaseBackup } from 'lucide-react'
import { useSources, useUpdateSources, useUpdateApiKeys, useConfig, useAiStatus, useAnalysisConfig, useSaveAnalysisConfig, useCycleStatus, useUpdateCycleConfig, useTriggerCycle, useRiskProfiles, useUpsertProfile, useDeleteProfile, useExitDefaults, useSaveExitDefaults, useOptionsGate, useSaveOptionsGate, useTechIndicatorsConfig, useSaveTechIndicatorsConfig, useInstrumentsWatchlist, useAddWatchlistInstrument, useRemoveWatchlistInstrument, validateTicker } from '../hooks/useApi'
import { Settings, Key, Globe, CheckCircle, XCircle, AlertCircle, Save, Eye, EyeOff, Brain, SlidersHorizontal, RefreshCw, Zap, Plus, Trash2, Pencil, X, Lock, Gauge, DollarSign, TrendingUp, ShieldAlert, DatabaseBackup, Radar } from 'lucide-react'
import clsx from 'clsx'
const API = ''
@@ -330,6 +330,85 @@ function RiskProfilesCard() {
)
}
function WatchlistCard() {
const { data: items, isLoading } = useInstrumentsWatchlist()
const { mutateAsync: addTicker, isPending: adding } = useAddWatchlistInstrument()
const { mutate: removeTicker } = useRemoveWatchlistInstrument()
const [input, setInput] = useState('')
const [error, setError] = useState('')
const handleAdd = async () => {
const sym = input.trim().toUpperCase()
if (!sym) return
setError('')
const check = await validateTicker(sym)
if (!check.valid) {
setError(check.reason ?? 'Ticker not found')
return
}
try {
await addTicker(sym)
setInput('')
} catch (e: unknown) {
const msg = (e as any)?.response?.data?.detail ?? (e as { message?: string })?.message ?? 'Could not add ticker'
setError(msg)
}
}
const list: any[] = items ?? []
return (
<div className="card bg-dark-700/20 mb-4">
<div className="flex items-center justify-between mb-3">
<div>
<div className="text-sm font-semibold text-slate-300 flex items-center gap-1.5">
<Radar className="w-4 h-4 text-blue-400" /> Instruments Watchlist
</div>
<div className="text-[10px] text-slate-600 mt-0.5">
Instruments surveillés pour le radar du Dashboard et les highlights Options Lab liste indépendante des autres watchlists (Markets, Options Lab, InstrumentModels).
</div>
</div>
</div>
<div className="flex items-center gap-2 mb-3">
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleAdd()}
placeholder="Ticker (ex. AAPL, EURUSD=X, ^GSPC)"
className="bg-dark-800 border border-slate-700/40 rounded px-3 py-1.5 text-xs text-white w-64 focus:outline-none focus:border-blue-500/50"
/>
<button onClick={handleAdd} disabled={adding || !input.trim()}
className="flex items-center gap-1 px-3 py-1.5 rounded text-xs bg-blue-600 text-white hover:bg-blue-500 disabled:opacity-40 transition-colors">
<Plus className="w-3.5 h-3.5" /> Add
</button>
</div>
{error && <div className="text-[10px] text-red-400 mb-3">{error}</div>}
{isLoading ? (
<div className="text-xs text-slate-600">Loading</div>
) : list.length === 0 ? (
<div className="text-xs text-slate-600">No instrument watched yet.</div>
) : (
<div className="space-y-1.5">
{list.map(item => (
<div key={item.ticker} className="flex items-center justify-between bg-dark-800/60 rounded px-3 py-1.5">
<div className="flex items-center gap-2 min-w-0">
<span className="text-xs font-mono font-bold text-white">{item.ticker}</span>
<span className="text-[10px] text-slate-500 truncate">{item.name}</span>
<span className="text-[9px] text-slate-700 bg-dark-700 px-1.5 py-0.5 rounded capitalize">{item.asset_class}</span>
</div>
<button onClick={() => removeTicker(item.ticker)} className="text-slate-600 hover:text-red-400 transition-colors">
<X className="w-3.5 h-3.5" />
</button>
</div>
))}
</div>
)}
</div>
)
}
export default function Config() {
const { data: sources, isLoading } = useSources()
const { data: config } = useConfig()
@@ -354,7 +433,7 @@ export default function Config() {
const [calendarRefreshH, setCalendarRefreshH] = useState(6)
const [showKeys, setShowKeys] = useState(false)
const [savedMsg, setSavedMsg] = useState('')
const [configTab, setConfigTab] = useState<'ia' | 'cycle' | 'sources' | 'options' | 'profiles' | 'data'>('ia')
const [configTab, setConfigTab] = useState<'ia' | 'cycle' | 'sources' | 'options' | 'profiles' | 'watchlist' | 'data'>('ia')
// IV gate local state
const [ivGateEnabled, setIvGateEnabled] = useState(true)
@@ -525,6 +604,7 @@ export default function Config() {
{ key: 'sources', label: 'Sources', icon: Globe },
{ key: 'options', label: 'Options — Settings', icon: TrendingUp },
{ key: 'profiles', label: 'Risk Profiles', icon: SlidersHorizontal },
{ key: 'watchlist', label: 'Watchlist', icon: Radar },
{ key: 'data', label: 'Data Management', icon: DatabaseBackup },
] as const
@@ -1542,6 +1622,9 @@ export default function Config() {
{/* ── Profils de risque ── */}
{configTab === 'profiles' && <RiskProfilesCard />}
{/* ── Watchlist ── */}
{configTab === 'watchlist' && <WatchlistCard />}
{/* ── Data Management ── */}
{configTab === 'data' && <DataManagementCard />}