'use client'; /** * Selected-satellite panel: bottom sheet on mobile, right column on desktop. Metadata from `/satellites/{norad}`, * live altitude/velocity polled from `/satellites/{norad}/live` every 5 s while open. */ import { ArrowUpRight, X } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { OrbitBadge, StatusBadge, TypeBadge } from '@/components/ui/badges'; import { clientApi } from '@/lib/client-api'; import { cn } from '@/lib/cn'; import { fmt2, fmtAgo, fmtKm, fmtMinutes } from '@/lib/format'; import { MISSION_LABELS, routes } from '@/lib/site'; import type { LivePosition, SatelliteRow } from '@/lib/types'; const LIVE_MS = 5_000; export interface PanelSnapshot { altitudeKm: number | null; velocityKmS: number | null; orbitClass: string | null; active: boolean | null; } function Row({ label, children }: { label: string; children: React.ReactNode }) { return (
{label} {children}
); } export function SatPanel({ norad, snapshot, onClose, className }: { norad: number; snapshot: PanelSnapshot; onClose: () => void; className?: string }) { const [sat, setSat] = useState(null); const [live, setLive] = useState(null); const [error, setError] = useState(null); useEffect(() => { setSat(null); setLive(null); setError(null); const ctrl = new AbortController(); clientApi .satellite(String(norad), ctrl.signal) .then((r) => setSat(r.data)) .catch((e: Error) => { if (e.name !== 'AbortError') setError('Metadata unavailable'); }); let timer: ReturnType | null = null; const poll = async () => { try { const r = await clientApi.live(String(norad), ctrl.signal); setLive(r.data); } catch { /* keep last */ } finally { if (!ctrl.signal.aborted) timer = setTimeout(poll, LIVE_MS); } }; void poll(); return () => { ctrl.abort(); if (timer) clearTimeout(timer); }; }, [norad]); const alt = live?.altitude_km ?? snapshot.altitudeKm; const vel = live?.velocity_km_s ?? snapshot.velocityKmS; const name = sat?.name ?? (error ? `NORAD ${norad}` : null); return ( ); }