/**
* PSKReporter Panel
* Shows where your digital mode signals are being received
* Uses MQTT WebSocket for real-time data
*/
import React, { useState } from 'react';
import { usePSKReporter } from '../hooks/usePSKReporter.js';
import { getBandColor } from '../utils/callsign.js';
const PSKReporterPanel = ({ callsign, onShowOnMap, showOnMap, onToggleMap }) => {
const [timeWindow] = useState(15); // Keep spots for 15 minutes
const [activeTab, setActiveTab] = useState('tx'); // Default to 'tx' (Being Heard)
const {
txReports,
txCount,
rxReports,
rxCount,
loading,
error,
connected,
source,
refresh
} = usePSKReporter(callsign, {
minutes: timeWindow,
enabled: callsign && callsign !== 'N0CALL'
});
const reports = activeTab === 'tx' ? txReports : rxReports;
// Get band color from frequency
const getFreqColor = (freqMHz) => {
if (!freqMHz) return 'var(--text-muted)';
const freq = parseFloat(freqMHz);
return getBandColor(freq);
};
// Format age
const formatAge = (minutes) => {
if (minutes < 1) return 'now';
if (minutes < 60) return `${minutes}m`;
return `${Math.floor(minutes/60)}h`;
};
// Get status indicator
const getStatusIndicator = () => {
if (connected) {
return β LIVE;
}
if (source === 'connecting' || source === 'reconnecting') {
return β {source};
}
if (error) {
return β offline;
}
return null;
};
if (!callsign || callsign === 'N0CALL') {
return (
{/* Header - matches DX Cluster style */}
π‘ PSKReporter {getStatusIndicator()}
{onToggleMap && (
)}
{/* Tabs - compact style */}
{/* Reports list - matches DX Cluster style */}
{error && !connected ? (
β οΈ Connection failed - click π to retry
) : loading && reports.length === 0 ? (
) : !connected && reports.length === 0 ? (
Waiting for connection...
) : reports.length === 0 ? (
{activeTab === 'tx'
? 'Waiting for spots... (TX to see reports)'
: 'No stations heard yet'}
) : (
{reports.slice(0, 20).map((report, i) => {
const freqMHz = report.freqMHz || (report.freq ? (report.freq / 1000000).toFixed(3) : '?');
const color = getFreqColor(freqMHz);
const displayCall = activeTab === 'tx' ? report.receiver : report.sender;
const grid = activeTab === 'tx' ? report.receiverGrid : report.senderGrid;
return (
onShowOnMap && report.lat && report.lon && onShowOnMap(report)}
style={{
display: 'grid',
gridTemplateColumns: '55px 1fr auto',
gap: '6px',
padding: '4px 6px',
borderRadius: '3px',
marginBottom: '2px',
background: i % 2 === 0 ? 'rgba(255,255,255,0.03)' : 'transparent',
cursor: report.lat && report.lon ? 'pointer' : 'default',
transition: 'background 0.15s',
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'}
>
{freqMHz}
{displayCall}
{grid && {grid}}
{report.mode}
{report.snr !== null && report.snr !== undefined && (
= 0 ? '#4ade80' : report.snr >= -10 ? '#fbbf24' : '#f97316',
fontWeight: '600'
}}>
{report.snr > 0 ? '+' : ''}{report.snr}
)}
{formatAge(report.age)}
);
})}
)}
);
};
export default PSKReporterPanel;
export { PSKReporterPanel };