Files
beacon-web/src/features/packets/PacketRow.tsx
T

85 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { formatHex } from "../../lib/formatters";
import { Timestamp } from "../../components/Timestamp";
import type { PacketSummary } from "../../types/api";
import { Badge } from "../../components/Badge";
import { Tooltip } from "../../components/Tooltip";
import { payloadTypeVariant } from "../../components/badge-utils";
import { ScopeTag } from "../../components/ScopeTag";
import { PAYLOAD_TYPE_NAMES, type PayloadTypeValue } from "../../types/enums";
interface PacketRowProps {
packet: PacketSummary;
expanded: boolean;
isFresh?: boolean;
onToggle: () => void;
}
// selectable packet card; observations live in the analyzer drawer
export function PacketRow({ packet, expanded, isFresh, onToggle }: PacketRowProps) {
return (
<div
className={`group bg-bg-surface border rounded-md px-3.5 py-2.5 cursor-pointer ${
expanded
// squared bottom joins the expansion below into one unfolded card
? "border-primary bg-primary/10 rounded-b-none"
: isFresh
? "packet-fresh"
: "border-border hover:border-text-dim/30 hover:bg-bg-raised/50"
}`}
onClick={() => onToggle()}
aria-pressed={expanded}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onToggle();
}
}}
>
<div className="flex items-center gap-2.5">
<span className="font-mono text-xs font-semibold text-primary tracking-wider">
{formatHex(packet.packetHash)}
</span>
<Badge variant={payloadTypeVariant(packet.payloadType)}>
{PAYLOAD_TYPE_NAMES[packet.payloadType as PayloadTypeValue] ?? packet.payloadTypeName}
</Badge>
<Tooltip label={`Heard by ${packet.observationCount} observer${packet.observationCount === 1 ? "" : "s"}`}>
<span
className="font-mono text-[11px] text-primary font-semibold whitespace-nowrap bg-primary/6 px-1.5 rounded-sm"
aria-label={`Heard by ${packet.observationCount} observer${packet.observationCount === 1 ? "" : "s"}`}
>
×{packet.observationCount}
</span>
</Tooltip>
</div>
{packet.summary && <div className="mt-1 truncate text-[11px] text-text-bright" title={packet.summary}>{packet.summary}</div>}
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 mt-1 text-[11px] text-text-dim">
<span className="font-mono text-[11px] text-text-muted uppercase tracking-wider bg-text-muted/8 px-1.5 py-px rounded-sm">
{packet.routeTypeName || "Unknown"}
</span>
{packet.scope && (
<>
<span className="text-[6px] text-border" aria-hidden>·</span>
<ScopeTag>{packet.scope}</ScopeTag>
</>
)}
<span className="text-[6px] text-border" aria-hidden>·</span>
<Timestamp value={packet.lastHeardAt} />
{packet.latestObserver && (
<>
<span className="text-[6px] text-border" aria-hidden>·</span>
<span className="text-text-normal">{packet.latestObserver.displayName ?? packet.latestObserver.id.slice(0, 8)}</span>
<span className="text-[6px] text-border" aria-hidden>·</span>
<span className="font-mono font-bold text-primary text-[11px] tracking-wider">
{packet.latestObserver.iata}
</span>
</>
)}
</div>
</div>
);
}