'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 (
);
}