feat(monitor/frontend): ISS panel (live position + next-pass placeholder for phase 6)

Four-row layout: Lat/Lon (degree symbol, mono), Alt (km, mono tabular),
Vel (km/h with thousands separator, mono tabular), Next Pass (— stub).

Reads snapshot.iss_position directly (no per-panel store — current
position is the only thing the panel cares about, and the wheretheiss.at
collector pushes a complete state every 10s, so latest-event semantics
match what the panel needs). Real shape verified: latitude/longitude/
altitude/velocity/timestamp/fetched_at — all numeric except fetched_at
ISO string. Stale threshold 30s (3x the 10s poll cadence).

Next Pass row stays "—" muted --fg-3 — Phase 6 wires the SGP4-driven
user-observer-location pass prediction per spec §10.4. No "sign in to
add observer" CTA: operator UI doesn't onboard.

Plan 5 Task 21 Design QA: degree symbol + mono coords, alt/vel mono
tabular, no decorative ISS-tracing widget (the centerpiece globe owns
that), next-pass row visible-but-muted (never hidden).
This commit is contained in:
CarterPerez-dev 2026-05-03 09:40:02 -04:00
parent f0bb0dcb8d
commit 43df53f845
3 changed files with 101 additions and 0 deletions

View File

@ -6,6 +6,7 @@ import { BTCPanel } from '@/pages/panels/BTCPanel'
import { CVEVelocityPanel } from '@/pages/panels/CVEVelocityPanel'
import { DShieldPanel } from '@/pages/panels/DShieldPanel'
import { ETHPanel } from '@/pages/panels/ETHPanel'
import { ISSPanel } from '@/pages/panels/ISSPanel'
import { KEVPanel } from '@/pages/panels/KEVPanel'
import { RansomwarePanel } from '@/pages/panels/RansomwarePanel'
import { SpaceWeatherPanel } from '@/pages/panels/SpaceWeatherPanel'
@ -33,6 +34,7 @@ export function Dashboard(): React.ReactElement {
<BTCPanel />
<ETHPanel />
<SpaceWeatherPanel />
<ISSPanel />
</aside>
<section className={styles.center}>
<Globe />

View File

@ -0,0 +1,29 @@
// ©AngelaMos | 2026
// ISSPanel.module.scss
.row {
display: grid;
grid-template-columns: 90px 1fr;
gap: 8px;
padding: 4px 8px;
align-items: center;
font-family: var(--font-mono);
font-size: var(--type-body);
font-variant-numeric: tabular-nums;
}
.label {
font-family: var(--font-sans);
font-size: var(--type-label);
letter-spacing: var(--letter-spacing-label);
text-transform: uppercase;
color: var(--fg-3);
}
.value {
color: var(--fg-1);
}
.muted {
color: var(--fg-3);
}

View File

@ -0,0 +1,70 @@
// ©AngelaMos | 2026
// ISSPanel.tsx
import { useSnapshot } from '@/api/snapshot'
import styles from './ISSPanel.module.scss'
import { Panel } from './Panel'
const STALE_AFTER_MS = 30_000
const COORDS_DECIMALS = 2
const ALT_DECIMALS = 0
interface IssPositionData {
latitude: number
longitude: number
altitude: number
velocity: number
timestamp: number
fetched_at?: string
}
export function ISSPanel(): React.ReactElement {
const { data } = useSnapshot()
const iss = data?.iss_position as IssPositionData | undefined
const lastTickAt = iss?.fetched_at
? new Date(iss.fetched_at).getTime()
: undefined
const isStale =
lastTickAt !== undefined
? Date.now() - lastTickAt > STALE_AFTER_MS
: undefined
return (
<Panel
title="ISS"
subtitle="POSITION"
rawHref="https://wheretheiss.at/"
rawLabel="wheretheiss.at"
isStale={isStale}
lastTickAt={lastTickAt}
>
<div className={styles.row}>
<span className={styles.label}>Lat, Lon</span>
<span className={styles.value}>
{iss
? `${iss.latitude.toFixed(COORDS_DECIMALS)}°, ${iss.longitude.toFixed(COORDS_DECIMALS)}°`
: '—'}
</span>
</div>
<div className={styles.row}>
<span className={styles.label}>Alt</span>
<span className={styles.value}>
{iss ? `${iss.altitude.toFixed(ALT_DECIMALS)} km` : '—'}
</span>
</div>
<div className={styles.row}>
<span className={styles.label}>Vel</span>
<span className={styles.value}>
{iss ? `${Math.round(iss.velocity).toLocaleString()} km/h` : '—'}
</span>
</div>
<div className={styles.row}>
<span className={styles.label}>Next Pass</span>
<span className={styles.muted}></span>
</div>
</Panel>
)
}
ISSPanel.displayName = 'ISSPanel'