import { useState } from 'react'; import { useTonight } from '../hooks/useTonight'; import { useWeather, useForecast } from '../hooks/useWeather'; import { useTargets } from '../hooks/useTargets'; import { useStats } from '../hooks/useStats'; import GoNogo from '../components/weather/GoNogo'; import DewAlert from '../components/weather/DewAlert'; import MoonPhaseIcon from '../components/sky/MoonPhaseIcon'; import DetailDrawer from '../components/targets/DetailDrawer'; import type { Target } from '../api/types'; const FILTER_LABELS: Record = { sv220: 'HaOIII', c2: 'SII/OIII', sv260: 'LP', uvir: 'UV/IR', }; const CC_LABELS: Record = { 1: 'Clear', 2: 'Clear', 3: 'Mostly clear', 4: 'Partly cloudy', 5: 'Partly cloudy', 6: 'Cloudy', 7: 'Mostly cloudy', 8: 'Overcast', 9: 'Overcast', }; const CC_COLOR = (n: number) => n <= 2 ? 'var(--good)' : n <= 4 ? 'var(--teal)' : n <= 6 ? 'var(--warn)' : 'var(--danger)'; function fmtTime(utc?: string): string { if (!utc) return '—'; return new Date(utc).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Paris' }); } function fmtDuration(min?: number): string { if (!min) return '—'; const h = Math.floor(min / 60); const m = min % 60; return `${h}h ${m < 10 ? '0' : ''}${m}m`; } function fmtIntTotal(min: number): string { if (min < 60) return `${min} min`; const h = (min / 60).toFixed(1); return `${h} h`; } export default function Dashboard() { const { data: tonight } = useTonight(); const { data: weather } = useWeather(); const { data: forecast } = useForecast(); const { data: targets } = useTargets({ tonight: true, limit: 5 }); const { data: stats } = useStats(); const [expandedTarget, setExpandedTarget] = useState(null); const moonPct = tonight?.moon_illumination != null ? `${Math.round(tonight.moon_illumination * 100)}%` : '—'; // Next 4 forecast slots for mini weather bar (3h each = 12h ahead) const slots = (forecast as { dataseries?: { cloudcover?: number; seeing?: number; timepoint?: number }[] })?.dataseries?.slice(0, 8) ?? []; return (

Dashboard

{tonight?.date && ( {tonight.date} )}
{/* Dew alert banner */} {weather?.dew_alert && (
)} {/* Stat cards row */}
{/* Go/No-go */}
Tonight
{weather?.temp_c != null && (
{weather.temp_c.toFixed(1)}°C · {weather.humidity_pct?.toFixed(0)}% RH
)}
{/* Moon */}
Moon
{tonight?.moon_illumination != null && }
{moonPct}
{tonight?.moon_phase_name ?? '—'}
{/* True dark */}
True Dark
{fmtDuration(tonight?.true_dark_minutes)}
{tonight?.true_dark_start_utc ? `${fmtTime(tonight.true_dark_start_utc)} – ${fmtTime(tonight.true_dark_end_utc)}` : 'No full dark tonight'}
{/* Stats summary */}
Logbook
{stats ? fmtIntTotal(stats.total_integration_min) : '—'}
{stats ? `${stats.total_sessions} sessions · ${stats.objects_with_keeper} keepers` : 'No data yet'}
{/* Tonight timing + top targets + forecast */}
{/* Tonight timing */}
Tonight's Window
{[ ['Dusk', fmtTime(tonight?.astro_dusk_utc)], ['Dawn', fmtTime(tonight?.astro_dawn_utc)], ['Moon rise', fmtTime(tonight?.moon_rise_utc)], ['Moon set', fmtTime(tonight?.moon_set_utc)], ['Dark start', fmtTime(tonight?.true_dark_start_utc)], ['Dark end', fmtTime(tonight?.true_dark_end_utc)], ].map(([label, value]) => ( ))}
{label} {value}
{/* Top targets */}
Top Targets Tonight
{!targets?.items?.length && (
Catalog loading…
)} {targets?.items?.map((t, i) => (
setExpandedTarget(expandedTarget?.id === t.id ? null : t)} style={{ display: 'flex', alignItems: 'center', padding: '8px 14px', borderBottom: '1px solid var(--border)', gap: 10, cursor: 'pointer', background: expandedTarget?.id === t.id ? 'var(--bg-hover)' : 'transparent', transition: 'background 0.1s', }} > {i + 1}
{t.common_name ?? t.name}
{t.name} · {t.usable_min ? `${t.usable_min}min` : '—'}
= 30 ? 'var(--good)' : 'var(--warn)' }}> {t.max_alt_deg?.toFixed(0)}° {t.recommended_filter && ( {FILTER_LABELS[t.recommended_filter] ?? t.recommended_filter.toUpperCase()} )}
{expandedTarget?.id === t.id && }
))}
{/* Forecast mini bars */}
24h Forecast
{slots.length === 0 && (
No forecast data
)}
{slots.map((slot, i) => { const cc = slot.cloudcover ?? 5; const seeing = slot.seeing ?? 5; const hoursAhead = (i + 1) * 3; const label = `+${hoursAhead}h`; return (
{label}
{CC_LABELS[cc] ?? '—'} {['', '0.5″', '0.75″', '1.0″', '1.25″', '1.5″', '2.0″', '2.5″', '>3″'][seeing] ?? '—'}
); })}
); }