diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 44267d8..dd9359a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "ag-grid-react": "^35.3.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "leaflet": "^1.9.4", "lucide-react": "^1.16.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -30,6 +31,7 @@ }, "devDependencies": { "@tailwindcss/vite": "^4.3.0", + "@types/leaflet": "^1.9.21", "@types/node": "^25.9.1", "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", @@ -2584,6 +2586,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "25.9.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", @@ -2995,6 +3014,12 @@ "node": ">=6" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index e20fe06..6abfc0d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "ag-grid-react": "^35.3.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "leaflet": "^1.9.4", "lucide-react": "^1.16.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -31,6 +32,7 @@ }, "devDependencies": { "@tailwindcss/vite": "^4.3.0", + "@types/leaflet": "^1.9.21", "@types/node": "^25.9.1", "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 206da80..98b7e46 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -687705a933fcf09f20bdb5083955a417 \ No newline at end of file +c98874941451e4e6ffa48f22c1d764e7 \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e58b5db..002d34e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -39,6 +39,7 @@ import { ConfirmDialog } from '@/components/ConfirmDialog'; import { SettingsModal } from '@/components/SettingsModal'; import { QSOEditModal } from '@/components/QSOEditModal'; import { BandMap } from '@/components/BandMap'; +import { MainMap } from '@/components/MainMap'; import { RecentQSOsGrid } from '@/components/RecentQSOsGrid'; import { ShutdownProgress } from '@/components/ShutdownProgress'; import { ClusterGrid } from '@/components/ClusterGrid'; @@ -561,6 +562,17 @@ export default function App() { const [clusterModeFilter, setClusterModeFilter] = useState>(new Set()); const [clusterSearch, setClusterSearch] = useState(''); const [showBandMap, setShowBandMap] = useState(false); + // Which side the band map docks to (persisted). Toggled from its header. + const [bandMapSide, setBandMapSide] = useState<'left' | 'right'>( + () => (localStorage.getItem('bandmap.side') === 'left' ? 'left' : 'right'), + ); + const toggleBandMapSide = useCallback(() => { + setBandMapSide((s) => { + const next = s === 'right' ? 'left' : 'right'; + localStorage.setItem('bandmap.side', next); + return next; + }); + }, []); type SortKey = 'time' | 'call' | 'freq' | 'band' | 'mode' | 'spotter' | 'source'; const [clusterSort, setClusterSort] = useState<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'time', dir: 'desc' }); // Cached per-call slot status: "new" | "new-band" | "new-slot" | "worked". @@ -2121,7 +2133,8 @@ export default function App() { {/* ===== LOWER: tabs+table | call history | (optional) band map ===== */} {compact ? null : <> -
+
@@ -2138,7 +2151,20 @@ export default function App() { )} Awards - Propagation + {/* Not a tab — QRZ blocks embedding, so this opens the call's + QRZ.com page in the system browser. Styled like a trigger. */} + {qslTabOpen && ( QSL Manager @@ -2581,19 +2607,28 @@ export default function App() { )} - {(['main','awards','propagation'] as const).map((t) => ( - - -
{t[0].toUpperCase() + t.slice(1)}
-
Module coming soon.
-
- ))} + + + + + + +
Awards
+
Module coming soon.
+
{showBandMap && ( -
+
s.band === band)} spotStatus={spotStatus} diff --git a/frontend/src/components/BandMap.tsx b/frontend/src/components/BandMap.tsx index e95c05a..dd9e6c9 100644 --- a/frontend/src/components/BandMap.tsx +++ b/frontend/src/components/BandMap.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState } from 'react'; -import { Minus, Plus, Crosshair, X } from 'lucide-react'; +import { Minus, Plus, Crosshair, X, PanelLeft, PanelRight } from 'lucide-react'; import { cn } from '@/lib/utils'; import { spotStatusKey, inferSpotMode, spotModeCategory } from '@/lib/spot'; @@ -32,6 +32,8 @@ interface Props { currentFreqHz: number; onSpotClick: (s: Spot) => void; onClose?: () => void; + side?: 'left' | 'right'; + onToggleSide?: () => void; } const BAND_RANGES: Record = { @@ -144,7 +146,7 @@ const BOT_PAD = 14; // the top-most freq label isn't clipped at y=0 // last; ties broken by closeness to the rig freq). const MAX_VISIBLE_SPOTS = 30; -export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, onClose }: Props) { +export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, onClose, side = 'right', onToggleSide }: Props) { const range = BAND_RANGES[band]; const segments = SEGMENT_COLORS[band] ?? []; const [zoomIdx, setZoomIdx] = useState(0); @@ -365,6 +367,13 @@ export function BandMap({ band, spots, spotStatus, currentFreqHz, onSpotClick, o title="Scroll to current rig frequency"> + {onToggleSide && ( + + )} {onClose && (