← back to Ken

src/app/dashboard/page.js

218 lines

'use client';
import { useState, useEffect } from 'react';

function StatCard({ label, value, sub, color, icon }) {
  return (
    <div className="card">
      <div className="flex items-start justify-between">
        <div>
          <p className="text-xs text-gray-500 uppercase tracking-wide mb-1">{label}</p>
          <p className={`stat-value ${color || 'text-white'}`}>{value}</p>
          {sub && <p className="text-xs text-gray-500 mt-1">{sub}</p>}
        </div>
        {icon && <div className="text-gray-600">{icon}</div>}
      </div>
    </div>
  );
}

export default function DashboardOverview() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function load() {
      try {
        const [dash, risk, bankroll] = await Promise.all([
          fetch('/api/dashboard').then(r => r.json()),
          fetch('/api/risk').then(r => r.json()),
          fetch('/api/bankroll').then(r => r.json()),
        ]);
        setData({ dash, risk, bankroll });
      } catch (err) {
        console.error('Dashboard load error:', err);
      }
      setLoading(false);
    }
    load();
    const interval = setInterval(load, 15000);
    return () => clearInterval(interval);
  }, []);

  if (loading) return (
    <div className="flex items-center justify-center h-64">
      <div className="animate-spin w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full" />
    </div>
  );

  const d = data?.dash || {};
  const r = data?.risk?.state || {};
  const config = r.config || {};
  const b = data?.bankroll || {};

  return (
    <div className="space-y-6">
      {/* Header */}
      <div className="flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold">Dashboard</h1>
          <p className="text-gray-500 text-sm mt-0.5">Weather micro-betting system overview</p>
        </div>
        <div className="flex items-center gap-3">
          <span className={`badge ${config.paper_mode !== false ? 'badge-yellow' : 'badge-green'}`}>
            {config.paper_mode !== false ? 'PAPER MODE' : 'LIVE MODE'}
          </span>
          {r.kill_switch_active && (
            <span className="badge badge-red">KILL SWITCH ON</span>
          )}
          <span className={`w-2.5 h-2.5 rounded-full ${r.kill_switch_active ? 'bg-red-500' : 'bg-green-500 pulse-green'}`} />
        </div>
      </div>

      {/* Stats Grid */}
      <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
        <StatCard label="Bankroll" value={`$${parseFloat(r.bankroll || 50).toFixed(2)}`} color="text-green-400" sub="Starting: $50.00" />
        <StatCard label="Daily P&L" value={`$${parseFloat(r.daily_pnl || 0).toFixed(2)}`} color={parseFloat(r.daily_pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'} sub={`Limit: -$${config.max_daily_loss_usd || 5}`} />
        <StatCard label="Open Exposure" value={`$${parseFloat(r.open_exposure || 0).toFixed(2)}`} color="text-blue-400" sub={`Max: $${config.max_open_exposure_usd || 5}`} />
        <StatCard label="Max Drawdown" value={`${parseFloat(r.max_drawdown_pct || 0).toFixed(1)}%`} color="text-purple-400" sub={`Limit: ${config.max_drawdown_pct || 20}%`} />
      </div>

      {/* Trading + Markets Row */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        {/* Recent Predictions */}
        <div className="card">
          <h2 className="font-semibold mb-3 flex items-center gap-2">
            <svg className="w-4 h-4 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
            </svg>
            Recent Predictions
          </h2>
          {d.recent_predictions?.length > 0 ? (
            <table>
              <thead>
                <tr>
                  <th>Market</th>
                  <th>P(Yes)</th>
                  <th>Mid</th>
                  <th>Edge</th>
                  <th>Signal</th>
                </tr>
              </thead>
              <tbody>
                {d.recent_predictions.map((p, i) => (
                  <tr key={i}>
                    <td className="max-w-[180px] truncate">{p.condition_id?.substring(0, 12)}...</td>
                    <td className="font-mono">{(p.p_yes * 100).toFixed(1)}%</td>
                    <td className="font-mono">{p.market_mid ? (p.market_mid * 100).toFixed(1) + '%' : '-'}</td>
                    <td className={`font-mono ${p.edge > 0 ? 'text-green-400' : 'text-red-400'}`}>
                      {p.edge ? (p.edge * 100).toFixed(1) + '%' : '-'}
                    </td>
                    <td>
                      <span className={`badge ${p.recommendation === 'BUY_YES' || p.recommendation === 'BUY_NO' ? 'badge-green' : 'badge-yellow'}`}>
                        {p.recommendation || 'HOLD'}
                      </span>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          ) : (
            <p className="text-gray-500 text-sm">No predictions yet. Ingest markets to begin.</p>
          )}
        </div>

        {/* Recent Trades */}
        <div className="card">
          <h2 className="font-semibold mb-3 flex items-center gap-2">
            <svg className="w-4 h-4 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
            </svg>
            Recent Trades
          </h2>
          {d.recent_trades?.length > 0 ? (
            <table>
              <thead>
                <tr>
                  <th>Time</th>
                  <th>Side</th>
                  <th>Price</th>
                  <th>Size</th>
                  <th>P&L</th>
                </tr>
              </thead>
              <tbody>
                {d.recent_trades.map((t, i) => (
                  <tr key={i}>
                    <td className="text-gray-400">{new Date(t.match_time).toLocaleString()}</td>
                    <td><span className={`badge ${t.side === 'buy' ? 'badge-green' : 'badge-red'}`}>{t.side?.toUpperCase()}</span></td>
                    <td className="font-mono">${parseFloat(t.price).toFixed(4)}</td>
                    <td className="font-mono">${parseFloat(t.size_usd || 0).toFixed(2)}</td>
                    <td className={`font-mono ${parseFloat(t.pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}>
                      ${parseFloat(t.pnl || 0).toFixed(2)}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          ) : (
            <p className="text-gray-500 text-sm">No trades yet. System is in paper mode.</p>
          )}
        </div>
      </div>

      {/* System Info Row */}
      <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
        <div className="card">
          <h3 className="text-xs text-gray-500 uppercase mb-2">Markets Tracked</h3>
          <p className="stat-value text-blue-400">{d.market_count || 0}</p>
          <p className="text-xs text-gray-500 mt-1">{d.weather_market_count || 0} weather markets</p>
        </div>
        <div className="card">
          <h3 className="text-xs text-gray-500 uppercase mb-2">Forecast Runs</h3>
          <p className="stat-value text-purple-400">{d.forecast_count || 0}</p>
          <p className="text-xs text-gray-500 mt-1">NWS + GEFS sources</p>
        </div>
        <div className="card">
          <h3 className="text-xs text-gray-500 uppercase mb-2">Risk Config</h3>
          <div className="space-y-1 text-sm">
            <div className="flex justify-between"><span className="text-gray-500">Max Bet</span><span>${config.max_bet_usd || 0.50}</span></div>
            <div className="flex justify-between"><span className="text-gray-500">Min Edge</span><span>{((config.min_edge_threshold || 0.03) * 100).toFixed(0)}%</span></div>
            <div className="flex justify-between"><span className="text-gray-500">Kelly Fraction</span><span>{config.fractional_kelly || 0.10}x</span></div>
          </div>
        </div>
      </div>

      {/* Alerts */}
      <div className="card">
        <h2 className="font-semibold mb-3 flex items-center gap-2">
          <svg className="w-4 h-4 text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
          </svg>
          System Alerts
        </h2>
        {d.recent_alerts?.length > 0 ? (
          <div className="space-y-2">
            {d.recent_alerts.map((a, i) => (
              <div key={i} className={`flex items-start gap-3 p-3 rounded-lg ${
                a.severity === 'critical' ? 'bg-red-500/10' :
                a.severity === 'warning' ? 'bg-yellow-500/10' : 'bg-blue-500/10'
              }`}>
                <span className={`badge ${
                  a.severity === 'critical' ? 'badge-red' :
                  a.severity === 'warning' ? 'badge-yellow' : 'badge-blue'
                }`}>{a.severity}</span>
                <div>
                  <p className="text-sm">{a.message}</p>
                  <p className="text-xs text-gray-500 mt-0.5">{new Date(a.ts).toLocaleString()}</p>
                </div>
              </div>
            ))}
          </div>
        ) : (
          <p className="text-gray-500 text-sm">No alerts. System is healthy.</p>
        )}
      </div>
    </div>
  );
}