← back to Ken
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 }}>‣</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 }}>‣</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>
);
}