← back to Bertha

kalshi-dash/src/pages/Weather.jsx

172 lines

import React, { useState, useEffect } from 'react';
import { post } from '../api';

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

  useEffect(() => { load(); }, []);

  async function load() {
    setLoading(true);
    try {
      const r = await post('/weather', { action: 'get_data' });
      setData(r);
    } catch {}
    setLoading(false);
  }

  async function fetchAll() {
    setLoading(true);
    try {
      const r = await post('/weather', { action: 'fetch_all' });
      setData(r);
    } catch {}
    setLoading(false);
  }

  if (loading) return (
    <div className="flex items-center justify-center h-64">
      <div
        className="w-8 h-8 rounded-full border-2 border-t-transparent animate-spin"
        style={{ borderColor: 'var(--cyan)', borderTopColor: 'transparent' }}
      />
    </div>
  );

  const metricCards = [
    { label: 'Stations',     key: 'stations',     icon: '\u{1F4CD}', color: 'var(--cyan)' },
    { label: 'Forecasts',    key: 'forecasts',    icon: '\u{1F52E}', color: 'var(--blue)' },
    { label: 'Observations', key: 'observations', icon: '\u{1F4CA}', color: 'var(--green)' },
    { label: 'Metrics',      key: 'metrics',      icon: '\u{1F4CF}', color: 'var(--purple)' },
  ];

  return (
    <div className="space-y-6">
      {/* Page Header */}
      <div className="flex items-center justify-between">
        <div>
          <h1 className="heading text-2xl font-bold flex items-center gap-2">
            <span style={{ color: 'var(--cyan)' }}>{'\u2601'}</span>
            <span className="gradient-cyan">Weather Markets</span>
          </h1>
          <p className="section-subtitle mt-1">NWS settlement data for Ken weather contracts</p>
        </div>
        <button onClick={fetchAll} className="btn btn-p">
          {'\u21BB'} Fetch Latest
        </button>
      </div>

      {/* Metric Summary Cards */}
      <div className="grid grid-cols-4 gap-4">
        {metricCards.map(({ label, key, icon, color }) => (
          <div key={label} className="card">
            <div className="flex items-center gap-2 mb-2">
              <span className="text-lg">{icon}</span>
              <p className="stat-label">{label}</p>
            </div>
            <p className="stat-v" style={{ color }}>{data?.[key] ?? '—'}</p>
          </div>
        ))}
      </div>

      {/* Settlement Rule Reference */}
      <div className="card">
        <h2 className="section-title mb-4">Settlement Rule Reference</h2>
        <div className="grid grid-cols-2 gap-6">
          <div
            className="rounded-lg p-4"
            style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
          >
            <p className="stat-label mb-3">Temperature Contracts</p>
            <ul className="space-y-2">
              {[
                'Source: NWS Daily Climate Report (final)',
                'Station: Market-specified (e.g. KORD, KLGA)',
                'Values: Tmax/Tmin in degrees F',
                'Missing data: treated as 0',
              ].map((item, i) => (
                <li key={i} className="flex items-start gap-2 text-xs" style={{ color: 'var(--text-dim)' }}>
                  <span style={{ color: 'var(--cyan)', flexShrink: 0 }}>&#8227;</span>
                  {item}
                </li>
              ))}
            </ul>
          </div>
          <div
            className="rounded-lg p-4"
            style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
          >
            <p className="stat-label mb-3">Precipitation Contracts</p>
            <ul className="space-y-2">
              {[
                'Source: NWS Daily Climate Report (first complete)',
                'Trace: counted as 0.00 inches',
                'Missing: counted as 0.00 inches',
                'Revisions: do NOT change settlement',
              ].map((item, i) => (
                <li key={i} className="flex items-start gap-2 text-xs" style={{ color: 'var(--text-dim)' }}>
                  <span style={{ color: 'var(--blue)', flexShrink: 0 }}>&#8227;</span>
                  {item}
                </li>
              ))}
            </ul>
          </div>
        </div>
      </div>

      {/* Recent Observations Table */}
      {(data?.outcomes || []).length > 0 && (
        <div className="card overflow-x-auto">
          <h2 className="section-title mb-4">Recent Observations</h2>
          <table>
            <thead>
              <tr>
                <th>Station</th>
                <th>Metric</th>
                <th>Value</th>
                <th>Source</th>
                <th>Date</th>
              </tr>
            </thead>
            <tbody>
              {data.outcomes.map((o, i) => (
                <tr key={i} className="fade-in">
                  <td className="mono text-sm" style={{ color: 'var(--cyan)' }}>{o.station_id}</td>
                  <td>
                    <span className="badge bg-blue">{o.metric}</span>
                  </td>
                  <td className="mono font-bold" style={{ color: 'var(--text)' }}>
                    {o.value}{o.units}
                  </td>
                  <td className="text-xs" style={{ color: 'var(--text-muted)' }}>{o.source}</td>
                  <td className="text-xs mono" style={{ color: 'var(--text-muted)' }}>{o.period_end}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {/* Empty state */}
      {(data?.outcomes || []).length === 0 && (
        <div
          className="card text-center py-12"
          style={{ border: '1px dashed var(--border)' }}
        >
          <p className="text-4xl mb-3">{'\u26C5'}</p>
          <p className="heading text-sm font-semibold mb-1" style={{ color: 'var(--text-dim)' }}>
            No observations loaded
          </p>
          <p className="text-xs mb-4" style={{ color: 'var(--text-muted)' }}>
            Click "Fetch Latest" to pull current NWS settlement data
          </p>
          <button onClick={fetchAll} className="btn btn-p btn-sm">
            Fetch Latest
          </button>
        </div>
      )}
    </div>
  );
}