map
This commit is contained in:
+45
-10
@@ -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<Set<SpotModeCat>>(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 : <>
|
||||
<div className={cn('grid gap-2.5 p-2.5 flex-1 min-h-0 grid-rows-[minmax(0,1fr)]', showBandMap ? 'grid-cols-[1fr_260px]' : 'grid-cols-[1fr]')}>
|
||||
<div className={cn('grid gap-2.5 p-2.5 flex-1 min-h-0 grid-rows-[minmax(0,1fr)]',
|
||||
showBandMap ? (bandMapSide === 'left' ? 'grid-cols-[260px_1fr]' : 'grid-cols-[1fr_260px]') : 'grid-cols-[1fr]')}>
|
||||
<section className="bg-card border border-border rounded-lg shadow-sm flex flex-col min-h-0 overflow-hidden">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex flex-col min-h-0 flex-1">
|
||||
<TabsList className="px-3 shrink-0">
|
||||
@@ -2138,7 +2151,20 @@ export default function App() {
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="awards">Awards</TabsTrigger>
|
||||
<TabsTrigger value="propagation">Propagation</TabsTrigger>
|
||||
{/* Not a tab — QRZ blocks embedding, so this opens the call's
|
||||
QRZ.com page in the system browser. Styled like a trigger. */}
|
||||
<button
|
||||
type="button"
|
||||
disabled={!callsign.trim()}
|
||||
title={callsign.trim() ? `Open ${callsign.trim().toUpperCase()} on QRZ.com` : 'Enter a callsign first'}
|
||||
onClick={() => {
|
||||
const c = callsign.trim().toUpperCase().split('/').map(encodeURIComponent).join('/');
|
||||
if (c) OpenExternalURL(`https://www.qrz.com/db/${c}`).catch((e) => setError(String(e?.message ?? e)));
|
||||
}}
|
||||
className="inline-flex items-center justify-center gap-1 whitespace-nowrap px-3 py-1.5 text-xs font-medium text-muted-foreground border-b-2 border-transparent transition-all hover:text-foreground disabled:pointer-events-none disabled:opacity-50 -mb-px"
|
||||
>
|
||||
QRZ.com ↗
|
||||
</button>
|
||||
{qslTabOpen && (
|
||||
<TabsTrigger value="qsl" className="gap-1.5">
|
||||
QSL Manager
|
||||
@@ -2581,19 +2607,28 @@ export default function App() {
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{(['main','awards','propagation'] as const).map((t) => (
|
||||
<TabsContent key={t} value={t} className="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-2 py-12">
|
||||
<Hash className="size-10 opacity-30" />
|
||||
<div className="text-base font-semibold text-foreground/70">{t[0].toUpperCase() + t.slice(1)}</div>
|
||||
<div className="text-xs">Module coming soon.</div>
|
||||
</TabsContent>
|
||||
))}
|
||||
<TabsContent value="main" className="flex-1 min-h-0 p-0">
|
||||
<MainMap
|
||||
fromGrid={station.my_grid}
|
||||
toGrid={grid}
|
||||
fromLabel={station.callsign}
|
||||
toLabel={callsign}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="awards" className="flex-1 flex flex-col items-center justify-center text-muted-foreground gap-2 py-12">
|
||||
<Hash className="size-10 opacity-30" />
|
||||
<div className="text-base font-semibold text-foreground/70">Awards</div>
|
||||
<div className="text-xs">Module coming soon.</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</section>
|
||||
|
||||
{showBandMap && (
|
||||
<div className="bg-card border border-border rounded-lg shadow-sm flex flex-col min-h-0 overflow-hidden">
|
||||
<div className={cn('bg-card border border-border rounded-lg shadow-sm flex flex-col min-h-0 overflow-hidden', bandMapSide === 'left' && 'order-first')}>
|
||||
<BandMap
|
||||
side={bandMapSide}
|
||||
onToggleSide={toggleBandMapSide}
|
||||
band={band}
|
||||
spots={spots.filter((s) => s.band === band)}
|
||||
spotStatus={spotStatus}
|
||||
|
||||
Reference in New Issue
Block a user