diff --git a/frontend/src/components/RepeaterDashboard.tsx b/frontend/src/components/RepeaterDashboard.tsx index 8a85afa..1134d62 100644 --- a/frontend/src/components/RepeaterDashboard.tsx +++ b/frontend/src/components/RepeaterDashboard.tsx @@ -5,6 +5,7 @@ import { useEffect, useMemo, type FormEvent, + type ReactNode, lazy, Suspense, } from 'react'; @@ -33,6 +34,7 @@ import type { NeighborInfo, } from '../types'; import { isValidLocation, calculateDistance, formatDistance } from '../utils/pathUtils'; +import { getMapFocusHash } from '../utils/urlHash'; // Lazy-load the entire mini-map file so react-leaflet imports are bundled together // and MapContainer only mounts once (avoids "already initialized" crash). @@ -838,11 +840,57 @@ export function RepeaterDashboard({ > {conversation.id} - {contact?.last_seen && ( - - (Last heard: {formatTime(contact.last_seen)}) - - )} + {contact && + (() => { + const parts: ReactNode[] = []; + if (contact.last_seen) { + parts.push(`Last heard: ${formatTime(contact.last_seen)}`); + } + if (contact.last_path_len === -1) { + parts.push('flood'); + } else if (contact.last_path_len === 0) { + parts.push('direct'); + } else if (contact.last_path_len > 0) { + parts.push(`${contact.last_path_len} hop${contact.last_path_len > 1 ? 's' : ''}`); + } + if (isValidLocation(contact.lat, contact.lon)) { + const distFromUs = + radioLat != null && radioLon != null && isValidLocation(radioLat, radioLon) + ? calculateDistance(radioLat, radioLon, contact.lat, contact.lon) + : null; + parts.push( + + { + e.stopPropagation(); + const url = + window.location.origin + + window.location.pathname + + getMapFocusHash(contact.public_key); + window.open(url, '_blank'); + }} + title="View on map" + > + {contact.lat!.toFixed(3)}, {contact.lon!.toFixed(3)} + + {distFromUs !== null && ` (${formatDistance(distFromUs)})`} + + ); + } + return parts.length > 0 ? ( + + ( + {parts.map((part, i) => ( + + {i > 0 && ', '} + {part} + + ))} + ) + + ) : null; + })()}
{loggedIn && (