psk styling and mapping

pull/37/head
accius 2 days ago
parent 14f9539eb4
commit ad987f79a4

@ -191,6 +191,7 @@ const App = () => {
const mySpots = useMySpots(config.callsign); const mySpots = useMySpots(config.callsign);
const satellites = useSatellites(config.location); const satellites = useSatellites(config.location);
const localWeather = useLocalWeather(config.location); const localWeather = useLocalWeather(config.location);
const pskReporter = usePSKReporter(config.callsign, { minutes: 15, enabled: config.callsign !== 'N0CALL' });
// Computed values // Computed values
const deGrid = useMemo(() => calculateGridSquare(config.location.lat, config.location.lon), [config.location]); const deGrid = useMemo(() => calculateGridSquare(config.location.lat, config.location.lon), [config.location]);
@ -460,11 +461,13 @@ const App = () => {
dxPaths={dxPaths.data} dxPaths={dxPaths.data}
dxFilters={dxFilters} dxFilters={dxFilters}
satellites={satellites.data} satellites={satellites.data}
pskReporterSpots={[...(pskReporter.txReports || []), ...(pskReporter.rxReports || [])]}
showDXPaths={mapLayers.showDXPaths} showDXPaths={mapLayers.showDXPaths}
showDXLabels={mapLayers.showDXLabels} showDXLabels={mapLayers.showDXLabels}
onToggleDXLabels={toggleDXLabels} onToggleDXLabels={toggleDXLabels}
showPOTA={mapLayers.showPOTA} showPOTA={mapLayers.showPOTA}
showSatellites={mapLayers.showSatellites} showSatellites={mapLayers.showSatellites}
showPSKReporter={mapLayers.showPSKReporter}
onToggleSatellites={toggleSatellites} onToggleSatellites={toggleSatellites}
hoveredSpot={hoveredSpot} hoveredSpot={hoveredSpot}
/> />
@ -596,11 +599,13 @@ const App = () => {
dxPaths={dxPaths.data} dxPaths={dxPaths.data}
dxFilters={dxFilters} dxFilters={dxFilters}
satellites={satellites.data} satellites={satellites.data}
pskReporterSpots={[...(pskReporter.txReports || []), ...(pskReporter.rxReports || [])]}
showDXPaths={mapLayers.showDXPaths} showDXPaths={mapLayers.showDXPaths}
showDXLabels={mapLayers.showDXLabels} showDXLabels={mapLayers.showDXLabels}
onToggleDXLabels={toggleDXLabels} onToggleDXLabels={toggleDXLabels}
showPOTA={mapLayers.showPOTA} showPOTA={mapLayers.showPOTA}
showSatellites={mapLayers.showSatellites} showSatellites={mapLayers.showSatellites}
showPSKReporter={mapLayers.showPSKReporter}
onToggleSatellites={toggleSatellites} onToggleSatellites={toggleSatellites}
hoveredSpot={hoveredSpot} hoveredSpot={hoveredSpot}
/> />
@ -620,9 +625,9 @@ const App = () => {
</div> </div>
{/* RIGHT SIDEBAR */} {/* RIGHT SIDEBAR */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', overflow: 'hidden' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: '4px', overflow: 'hidden' }}>
{/* DX Cluster - reduced size to make room for PSKReporter */} {/* DX Cluster - primary panel */}
<div style={{ flex: '1 1 0', minHeight: '180px', maxHeight: '220px', overflow: 'hidden' }}> <div style={{ flex: '1 1 auto', minHeight: '150px', overflow: 'hidden' }}>
<DXClusterPanel <DXClusterPanel
data={dxCluster.data} data={dxCluster.data}
loading={dxCluster.loading} loading={dxCluster.loading}
@ -637,10 +642,12 @@ const App = () => {
/> />
</div> </div>
{/* PSKReporter - where your digital signals are heard */} {/* PSKReporter - digital mode spots */}
<div style={{ flex: '1 1 0', minHeight: '180px', maxHeight: '220px', overflow: 'hidden' }}> <div style={{ flex: '1 1 auto', minHeight: '150px', overflow: 'hidden' }}>
<PSKReporterPanel <PSKReporterPanel
callsign={config.callsign} callsign={config.callsign}
showOnMap={mapLayers.showPSKReporter}
onToggleMap={togglePSKReporter}
onShowOnMap={(report) => { onShowOnMap={(report) => {
if (report.lat && report.lon) { if (report.lat && report.lon) {
setDxLocation({ lat: report.lat, lon: report.lon, call: report.receiver || report.sender }); setDxLocation({ lat: report.lat, lon: report.lon, call: report.receiver || report.sender });
@ -649,13 +656,13 @@ const App = () => {
/> />
</div> </div>
{/* DXpeditions - smaller */} {/* DXpeditions */}
<div style={{ flex: '0 0 auto', maxHeight: '120px', overflow: 'hidden' }}> <div style={{ flex: '0 0 auto', minHeight: '80px', maxHeight: '110px', overflow: 'hidden' }}>
<DXpeditionPanel data={dxpeditions.data} loading={dxpeditions.loading} /> <DXpeditionPanel data={dxpeditions.data} loading={dxpeditions.loading} />
</div> </div>
{/* POTA - smaller */} {/* POTA */}
<div style={{ flex: '0 0 auto', maxHeight: '100px', overflow: 'hidden' }}> <div style={{ flex: '0 0 auto', minHeight: '70px', maxHeight: '100px', overflow: 'hidden' }}>
<POTAPanel <POTAPanel
data={potaSpots.data} data={potaSpots.data}
loading={potaSpots.loading} loading={potaSpots.loading}
@ -664,8 +671,8 @@ const App = () => {
/> />
</div> </div>
{/* Contests - smaller */} {/* Contests */}
<div style={{ flex: '0 0 auto', maxHeight: '120px', overflow: 'hidden' }}> <div style={{ flex: '0 0 auto', minHeight: '70px', maxHeight: '100px', overflow: 'hidden' }}>
<ContestPanel data={contests.data} loading={contests.loading} /> <ContestPanel data={contests.data} loading={contests.loading} />
</div> </div>
</div> </div>

@ -1,129 +1,152 @@
/** /**
* PSKReporter Panel * PSKReporter Panel
* Shows where your digital mode signals are being received * Shows where your digital mode signals are being received
* Styled to match DXClusterPanel
*/ */
import React, { useState } from 'react'; import React, { useState } from 'react';
import { usePSKReporter } from '../hooks/usePSKReporter.js'; import { usePSKReporter } from '../hooks/usePSKReporter.js';
import { getBandColor } from '../utils/callsign.js';
const PSKReporterPanel = ({ callsign, onShowOnMap }) => { const PSKReporterPanel = ({ callsign, onShowOnMap, showOnMap, onToggleMap }) => {
const [timeWindow, setTimeWindow] = useState(15); // minutes const [timeWindow, setTimeWindow] = useState(15);
const [activeTab, setActiveTab] = useState('tx'); // 'tx' or 'rx' const [activeTab, setActiveTab] = useState('rx'); // Default to 'rx' (Hearing) - more useful
const { const {
txReports, txReports,
txCount, txCount,
rxReports, rxReports,
rxCount, rxCount,
stats,
loading, loading,
error, error,
lastUpdate,
refresh refresh
} = usePSKReporter(callsign, { } = usePSKReporter(callsign, {
minutes: timeWindow, minutes: timeWindow,
enabled: callsign && callsign !== 'N0CALL' enabled: callsign && callsign !== 'N0CALL'
}); });
const formatTime = (timestamp) => { const reports = activeTab === 'tx' ? txReports : rxReports;
const date = new Date(timestamp); const count = activeTab === 'tx' ? txCount : rxCount;
return date.toLocaleTimeString('en-US', {
hour: '2-digit', // Get band color from frequency
minute: '2-digit', const getFreqColor = (freqMHz) => {
hour12: false if (!freqMHz) return 'var(--text-muted)';
}) + 'z'; const freq = parseFloat(freqMHz);
return getBandColor(freq);
}; };
// Format age
const formatAge = (minutes) => { const formatAge = (minutes) => {
if (minutes < 1) return 'now'; if (minutes < 1) return 'now';
if (minutes === 1) return '1m ago'; if (minutes < 60) return `${minutes}m`;
return `${minutes}m ago`; return `${Math.floor(minutes/60)}h`;
}; };
const getSnrColor = (snr) => {
if (snr === null || snr === undefined) return 'var(--text-muted)';
if (snr >= 0) return '#4ade80'; // Green - excellent
if (snr >= -10) return '#fbbf24'; // Yellow - good
if (snr >= -15) return '#f97316'; // Orange - fair
return '#ef4444'; // Red - weak
};
const reports = activeTab === 'tx' ? txReports : rxReports;
const count = activeTab === 'tx' ? txCount : rxCount;
if (!callsign || callsign === 'N0CALL') { if (!callsign || callsign === 'N0CALL') {
return ( return (
<div className="panel"> <div className="panel" style={{ padding: '10px' }}>
<div className="panel-header"> <div style={{ fontSize: '12px', color: 'var(--accent-primary)', fontWeight: '700', marginBottom: '6px' }}>
<span className="panel-icon">📡</span> 📡 PSKReporter
<h3>PSKReporter</h3>
</div> </div>
<div className="panel-content"> <div style={{ color: 'var(--text-muted)', textAlign: 'center', padding: '10px', fontSize: '11px' }}>
<p style={{ color: 'var(--text-muted)', textAlign: 'center', padding: '20px' }}> Set callsign in Settings
Set your callsign in Settings to see PSKReporter data
</p>
</div> </div>
</div> </div>
); );
} }
return ( return (
<div className="panel"> <div className="panel" style={{
<div className="panel-header"> padding: '10px',
<span className="panel-icon">📡</span> display: 'flex',
<h3>PSKReporter</h3> flexDirection: 'column',
<div style={{ marginLeft: 'auto', display: 'flex', gap: '8px', alignItems: 'center' }}> height: '100%',
overflow: 'hidden'
}}>
{/* Header - matches DX Cluster style */}
<div style={{
fontSize: '12px',
color: 'var(--accent-primary)',
fontWeight: '700',
marginBottom: '6px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}>
<span>📡 PSKReporter</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<select <select
value={timeWindow} value={timeWindow}
onChange={(e) => setTimeWindow(parseInt(e.target.value))} onChange={(e) => setTimeWindow(parseInt(e.target.value))}
style={{ style={{
background: 'var(--bg-tertiary)', background: 'rgba(100, 100, 100, 0.3)',
border: '1px solid var(--border-color)', border: '1px solid #666',
color: '#aaa',
padding: '2px 4px',
borderRadius: '4px', borderRadius: '4px',
padding: '2px 6px', fontSize: '10px',
fontSize: '0.75rem', fontFamily: 'JetBrains Mono',
color: 'var(--text-primary)' cursor: 'pointer'
}} }}
> >
<option value={5}>5 min</option> <option value={5}>5m</option>
<option value={15}>15 min</option> <option value={15}>15m</option>
<option value={30}>30 min</option> <option value={30}>30m</option>
<option value={60}>1 hour</option> <option value={60}>1h</option>
</select> </select>
<button <button
onClick={refresh} onClick={refresh}
disabled={loading}
style={{ style={{
background: 'transparent', background: 'rgba(100, 100, 100, 0.3)',
border: 'none', border: '1px solid #666',
cursor: 'pointer', color: '#888',
fontSize: '0.9rem', padding: '2px 6px',
borderRadius: '4px',
fontSize: '10px',
cursor: loading ? 'not-allowed' : 'pointer',
opacity: loading ? 0.5 : 1 opacity: loading ? 0.5 : 1
}} }}
disabled={loading}
title="Refresh"
> >
🔄 🔄
</button> </button>
{onToggleMap && (
<button
onClick={onToggleMap}
style={{
background: showOnMap ? 'rgba(68, 136, 255, 0.3)' : 'rgba(100, 100, 100, 0.3)',
border: `1px solid ${showOnMap ? '#4488ff' : '#666'}`,
color: showOnMap ? '#4488ff' : '#888',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '10px',
fontFamily: 'JetBrains Mono',
cursor: 'pointer'
}}
>
🗺 {showOnMap ? 'ON' : 'OFF'}
</button>
)}
</div> </div>
</div> </div>
{/* Tabs */} {/* Tabs - compact style */}
<div style={{ <div style={{
display: 'flex', display: 'flex',
borderBottom: '1px solid var(--border-color)', gap: '4px',
background: 'var(--bg-tertiary)' marginBottom: '6px'
}}> }}>
<button <button
onClick={() => setActiveTab('tx')} onClick={() => setActiveTab('tx')}
style={{ style={{
flex: 1, flex: 1,
padding: '8px', padding: '4px 6px',
background: activeTab === 'tx' ? 'var(--bg-secondary)' : 'transparent', background: activeTab === 'tx' ? 'rgba(74, 222, 128, 0.2)' : 'rgba(100, 100, 100, 0.2)',
border: 'none', border: `1px solid ${activeTab === 'tx' ? '#4ade80' : '#555'}`,
borderBottom: activeTab === 'tx' ? '2px solid var(--accent-primary)' : '2px solid transparent', borderRadius: '3px',
color: activeTab === 'tx' ? 'var(--text-primary)' : 'var(--text-muted)', color: activeTab === 'tx' ? '#4ade80' : '#888',
cursor: 'pointer', cursor: 'pointer',
fontSize: '0.8rem', fontSize: '10px',
fontWeight: activeTab === 'tx' ? '600' : '400' fontFamily: 'JetBrains Mono'
}} }}
> >
📤 Being Heard ({txCount}) 📤 Being Heard ({txCount})
@ -132,169 +155,101 @@ const PSKReporterPanel = ({ callsign, onShowOnMap }) => {
onClick={() => setActiveTab('rx')} onClick={() => setActiveTab('rx')}
style={{ style={{
flex: 1, flex: 1,
padding: '8px', padding: '4px 6px',
background: activeTab === 'rx' ? 'var(--bg-secondary)' : 'transparent', background: activeTab === 'rx' ? 'rgba(96, 165, 250, 0.2)' : 'rgba(100, 100, 100, 0.2)',
border: 'none', border: `1px solid ${activeTab === 'rx' ? '#60a5fa' : '#555'}`,
borderBottom: activeTab === 'rx' ? '2px solid var(--accent-primary)' : '2px solid transparent', borderRadius: '3px',
color: activeTab === 'rx' ? 'var(--text-primary)' : 'var(--text-muted)', color: activeTab === 'rx' ? '#60a5fa' : '#888',
cursor: 'pointer', cursor: 'pointer',
fontSize: '0.8rem', fontSize: '10px',
fontWeight: activeTab === 'rx' ? '600' : '400' fontFamily: 'JetBrains Mono'
}} }}
> >
📥 Hearing ({rxCount}) 📥 Hearing ({rxCount})
</button> </button>
</div> </div>
<div className="panel-content" style={{ maxHeight: '300px', overflowY: 'auto' }}> {/* Reports list - matches DX Cluster style */}
{error ? ( {error ? (
<div style={{ textAlign: 'center', padding: '20px', color: 'var(--text-muted)' }}> <div style={{ textAlign: 'center', padding: '10px', color: 'var(--text-muted)', fontSize: '11px' }}>
<div style={{ marginBottom: '8px' }}> PSKReporter temporarily unavailable</div> Temporarily unavailable
<div style={{ fontSize: '0.7rem' }}>Will retry automatically</div> </div>
</div> ) : loading && reports.length === 0 ? (
) : loading && reports.length === 0 ? ( <div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
<div style={{ textAlign: 'center', padding: '20px', color: 'var(--text-muted)' }}> <div className="loading-spinner" />
Loading... </div>
</div> ) : reports.length === 0 ? (
) : reports.length === 0 ? ( <div style={{ textAlign: 'center', padding: '10px', color: 'var(--text-muted)', fontSize: '11px' }}>
<div style={{ textAlign: 'center', padding: '20px', color: 'var(--text-muted)' }}> No {activeTab === 'tx' ? 'reception reports' : 'stations heard'}
No {activeTab === 'tx' ? 'reception reports' : 'stations heard'} in the last {timeWindow} minutes </div>
<div style={{ fontSize: '0.65rem', marginTop: '8px' }}> ) : (
(Make sure you're transmitting digital modes like FT8) <div style={{
</div> flex: 1,
</div> overflow: 'auto',
) : ( fontSize: '12px',
<> fontFamily: 'JetBrains Mono, monospace'
{/* Summary stats for TX */} }}>
{activeTab === 'tx' && txCount > 0 && ( {reports.slice(0, 20).map((report, i) => {
<div style={{ const freqMHz = report.freqMHz || (report.freq ? (report.freq / 1000000).toFixed(3) : '?');
padding: '8px 12px', const color = getFreqColor(freqMHz);
background: 'var(--bg-tertiary)', const displayCall = activeTab === 'tx' ? report.receiver : report.sender;
borderRadius: '4px', const grid = activeTab === 'tx' ? report.receiverGrid : report.senderGrid;
marginBottom: '8px',
fontSize: '0.75rem' return (
}}> <div
<div style={{ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: '8px' }}> key={`${displayCall}-${report.freq}-${i}`}
<span> onClick={() => onShowOnMap && report.lat && report.lon && onShowOnMap(report)}
<strong style={{ color: 'var(--accent-primary)' }}>{txCount}</strong> stations hearing you style={{
</span> display: 'grid',
{stats.txBands.length > 0 && ( gridTemplateColumns: '55px 1fr auto',
<span> gap: '6px',
Bands: {stats.txBands.join(', ')} padding: '4px 6px',
</span> borderRadius: '3px',
)} marginBottom: '2px',
{stats.txModes.length > 0 && ( background: i % 2 === 0 ? 'rgba(255,255,255,0.03)' : 'transparent',
<span> cursor: report.lat && report.lon ? 'pointer' : 'default',
Modes: {stats.txModes.slice(0, 3).join(', ')} transition: 'background 0.15s',
</span> borderLeft: '2px solid transparent'
)} }}
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(68, 136, 255, 0.15)'}
onMouseLeave={(e) => e.currentTarget.style.background = i % 2 === 0 ? 'rgba(255,255,255,0.03)' : 'transparent'}
>
<div style={{ color, fontWeight: '600', fontSize: '11px' }}>
{freqMHz}
</div> </div>
</div> <div style={{
)} color: 'var(--text-primary)',
fontWeight: '600',
{/* Reports list */} overflow: 'hidden',
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}> textOverflow: 'ellipsis',
{reports.slice(0, 25).map((report, idx) => ( whiteSpace: 'nowrap',
<div fontSize: '11px'
key={idx} }}>
onClick={() => onShowOnMap && report.lat && report.lon && onShowOnMap(report)} {displayCall}
style={{ {grid && <span style={{ color: 'var(--text-muted)', fontWeight: '400', marginLeft: '4px', fontSize: '9px' }}>{grid}</span>}
display: 'grid', </div>
gridTemplateColumns: '1fr auto auto auto', <div style={{
gap: '8px', display: 'flex',
padding: '6px 8px', alignItems: 'center',
background: 'var(--bg-tertiary)', gap: '4px',
borderRadius: '4px', fontSize: '10px'
fontSize: '0.75rem', }}>
cursor: report.lat && report.lon ? 'pointer' : 'default', <span style={{ color: 'var(--text-muted)' }}>{report.mode}</span>
alignItems: 'center' {report.snr !== null && report.snr !== undefined && (
}}
>
<div>
<span style={{
fontWeight: '600',
color: 'var(--accent-primary)',
fontFamily: 'var(--font-mono)'
}}>
{activeTab === 'tx' ? report.receiver : report.sender}
</span>
{(activeTab === 'tx' ? report.receiverGrid : report.senderGrid) && (
<span style={{
marginLeft: '6px',
color: 'var(--text-muted)',
fontSize: '0.7rem'
}}>
{activeTab === 'tx' ? report.receiverGrid : report.senderGrid}
</span>
)}
</div>
<div style={{
color: 'var(--text-secondary)',
fontFamily: 'var(--font-mono)'
}}>
{report.freqMHz} {report.band}
</div>
<div style={{
color: 'var(--text-muted)',
minWidth: '40px',
textAlign: 'center'
}}>
{report.mode}
</div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
minWidth: '70px',
justifyContent: 'flex-end'
}}>
{report.snr !== null && (
<span style={{
color: getSnrColor(report.snr),
fontFamily: 'var(--font-mono)',
fontWeight: '600'
}}>
{report.snr > 0 ? '+' : ''}{report.snr}dB
</span>
)}
<span style={{ <span style={{
color: 'var(--text-muted)', color: report.snr >= 0 ? '#4ade80' : report.snr >= -10 ? '#fbbf24' : '#f97316',
fontSize: '0.65rem' fontWeight: '600'
}}> }}>
{formatAge(report.age)} {report.snr > 0 ? '+' : ''}{report.snr}
</span> </span>
</div> )}
<span style={{ color: 'var(--text-muted)', fontSize: '9px' }}>
{formatAge(report.age)}
</span>
</div> </div>
))}
</div>
{reports.length > 25 && (
<div style={{
textAlign: 'center',
padding: '8px',
color: 'var(--text-muted)',
fontSize: '0.7rem'
}}>
Showing 25 of {reports.length} reports
</div> </div>
)} );
</> })}
)}
</div>
{/* Footer with last update */}
{lastUpdate && (
<div style={{
padding: '4px 12px',
borderTop: '1px solid var(--border-color)',
fontSize: '0.65rem',
color: 'var(--text-muted)',
textAlign: 'right'
}}>
Updated: {lastUpdate.toLocaleTimeString()}
</div> </div>
)} )}
</div> </div>

@ -21,11 +21,13 @@ export const WorldMap = ({
dxPaths, dxPaths,
dxFilters, dxFilters,
satellites, satellites,
pskReporterSpots,
showDXPaths, showDXPaths,
showDXLabels, showDXLabels,
onToggleDXLabels, onToggleDXLabels,
showPOTA, showPOTA,
showSatellites, showSatellites,
showPSKReporter,
onToggleSatellites, onToggleSatellites,
hoveredSpot hoveredSpot
}) => { }) => {
@ -44,6 +46,7 @@ export const WorldMap = ({
const dxPathsMarkersRef = useRef([]); const dxPathsMarkersRef = useRef([]);
const satMarkersRef = useRef([]); const satMarkersRef = useRef([]);
const satTracksRef = useRef([]); const satTracksRef = useRef([]);
const pskMarkersRef = useRef([]);
// Load map style from localStorage // Load map style from localStorage
const getStoredMapSettings = () => { const getStoredMapSettings = () => {
@ -416,6 +419,55 @@ export const WorldMap = ({
} }
}, [satellites, showSatellites]); }, [satellites, showSatellites]);
// Update PSKReporter markers
useEffect(() => {
if (!mapInstanceRef.current) return;
const map = mapInstanceRef.current;
pskMarkersRef.current.forEach(m => map.removeLayer(m));
pskMarkersRef.current = [];
if (showPSKReporter && pskReporterSpots && pskReporterSpots.length > 0 && deLocation) {
pskReporterSpots.forEach(spot => {
if (spot.lat && spot.lon) {
const displayCall = spot.receiver || spot.sender;
const freqMHz = spot.freqMHz || (spot.freq ? (spot.freq / 1000000).toFixed(3) : '?');
const bandColor = getBandColor(parseFloat(freqMHz));
// Draw line from DE to spot location
const points = getGreatCirclePoints(
[deLocation.lat, deLocation.lon],
[spot.lat, spot.lon],
50
);
const line = L.polyline(points, {
color: bandColor,
weight: 1.5,
opacity: 0.5,
dashArray: '4, 4'
}).addTo(map);
pskMarkersRef.current.push(line);
// Add small dot marker at spot location
const circle = L.circleMarker([spot.lat, spot.lon], {
radius: 4,
fillColor: bandColor,
color: '#fff',
weight: 1,
opacity: 0.9,
fillOpacity: 0.8
}).bindPopup(`
<b>${displayCall}</b><br>
${spot.mode} @ ${freqMHz} MHz<br>
${spot.snr !== null ? `SNR: ${spot.snr > 0 ? '+' : ''}${spot.snr} dB` : ''}
`).addTo(map);
pskMarkersRef.current.push(circle);
}
});
}
}, [pskReporterSpots, showPSKReporter, deLocation]);
return ( return (
<div style={{ position: 'relative', height: '100%', minHeight: '200px' }}> <div style={{ position: 'relative', height: '100%', minHeight: '200px' }}>
<div ref={mapRef} style={{ height: '100%', width: '100%', borderRadius: '8px' }} /> <div ref={mapRef} style={{ height: '100%', width: '100%', borderRadius: '8px' }} />

Loading…
Cancel
Save

Powered by TurnKey Linux.