/** * PSKReporter Panel * Shows where your digital mode signals are being received * Styled to match DXClusterPanel */ 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, setTimeWindow] = useState(15); const [activeTab, setActiveTab] = useState('rx'); // Default to 'rx' (Hearing) - more useful const { txReports, txCount, rxReports, rxCount, loading, error, refresh } = usePSKReporter(callsign, { minutes: timeWindow, enabled: callsign && callsign !== 'N0CALL' }); const reports = activeTab === 'tx' ? txReports : rxReports; const count = activeTab === 'tx' ? txCount : rxCount; // 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`; }; if (!callsign || callsign === 'N0CALL') { return (
πŸ“‘ PSKReporter
Set callsign in Settings
); } return (
{/* Header - matches DX Cluster style */}
πŸ“‘ PSKReporter
{onToggleMap && ( )}
{/* Tabs - compact style */}
{/* Reports list - matches DX Cluster style */} {error ? (
⚠️ Temporarily unavailable
) : loading && reports.length === 0 ? (
) : reports.length === 0 ? (
No {activeTab === 'tx' ? 'reception reports' : 'stations heard'}
) : (
{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 };