diff --git a/public/index.html b/public/index.html index d4f29b9..7f4c9d7 100644 --- a/public/index.html +++ b/public/index.html @@ -1205,24 +1205,34 @@ // ============================================ // PROPAGATION PANEL COMPONENT (Toggleable views) // ============================================ - const PropagationPanel = ({ propagation, loading }) => { + const PropagationPanel = ({ propagation, loading, bandConditions }) => { // Load view mode preference from localStorage, default to 'chart' const [viewMode, setViewMode] = useState(() => { try { const saved = localStorage.getItem('openhamclock_voacapViewMode'); - return saved === 'bars' ? 'bars' : 'chart'; // Default to chart + if (saved === 'bars' || saved === 'bands') return saved; + return 'chart'; // Default to chart } catch (e) { return 'chart'; } }); - // Save view mode preference when changed - const toggleViewMode = () => { - const newMode = viewMode === 'bars' ? 'chart' : 'bars'; + // Cycle through view modes: chart -> bars -> bands -> chart + const cycleViewMode = () => { + const modes = ['chart', 'bars', 'bands']; + const currentIdx = modes.indexOf(viewMode); + const newMode = modes[(currentIdx + 1) % modes.length]; setViewMode(newMode); try { localStorage.setItem('openhamclock_voacapViewMode', newMode); - } catch (e) { console.error('Failed to save VOACAP view mode:', e); } + } catch (e) { console.error('Failed to save view mode:', e); } }; + // Get band condition color/style + const getBandStyle = (condition) => ({ + GOOD: { bg: 'rgba(0,255,136,0.2)', color: '#00ff88', border: 'rgba(0,255,136,0.4)' }, + FAIR: { bg: 'rgba(255,180,50,0.2)', color: '#ffb432', border: 'rgba(255,180,50,0.4)' }, + POOR: { bg: 'rgba(255,68,102,0.2)', color: '#ff4466', border: 'rgba(255,68,102,0.4)' } + }[condition] || { bg: 'rgba(255,180,50,0.2)', color: '#ffb432', border: 'rgba(255,180,50,0.4)' }); + if (loading || !propagation) { return (
@@ -1270,183 +1280,224 @@ }; const bands = ['80m', '40m', '30m', '20m', '17m', '15m', '12m', '10m']; + + const viewModeLabels = { + chart: '▤ chart', + bars: '▦ bars', + bands: '◫ bands' + }; return ( -
+
- 📡 VOACAP {hasRealData && } - - {viewMode === 'bars' ? '▦ bars' : '▤ chart'} • click to toggle + + {viewMode === 'bands' ? '📊 BAND CONDITIONS' : '📡 VOACAP'} + {hasRealData && viewMode !== 'bands' && } -
- - {/* MUF/LUF and Data Source Info */} -
-
- - MUF - {muf || '?'} - MHz - - - LUF - {luf || '?'} - MHz - -
- - {hasRealData ? `📡 ${ionospheric?.source || 'ionosonde'}` : '⚡ estimated'} + + {viewModeLabels[viewMode]} • click to toggle
- {viewMode === 'chart' ? ( - /* VOACAP Heat Map Chart View */ + {viewMode === 'bands' ? ( + /* Band Conditions Grid View */
- {/* Heat map grid */} -
- {bands.map((band, bandIdx) => ( - - {/* Band label */} -
+ {(bandConditions?.data || []).slice(0, 12).map((band, idx) => { + const style = getBandStyle(band.condition); + return ( +
- {band.replace('m', '')} +
+ {band.band} +
+
+ {band.condition} +
- {/* 24 hour cells */} - {Array.from({ length: 24 }, (_, hour) => { - const bandData = hourlyPredictions?.[band]; - const hourData = bandData?.find(h => h.hour === hour); - const rel = hourData?.reliability || 0; - return ( -
- ); - })} - - ))} -
- - {/* Hour labels */} -
-
UTC
- {[0, '', '', 3, '', '', 6, '', '', 9, '', '', 12, '', '', 15, '', '', 18, '', '', 21, '', ''].map((h, i) => ( -
{h}
- ))} + ); + })}
- - {/* Legend & Info */} -
-
- REL: -
-
-
-
-
-
-
-
- {Math.round(distance)}km • {ionospheric?.foF2 ? `foF2=${ionospheric.foF2}` : `SSN=${solarData.ssn}`} -
+
+ SFI {solarData.sfi} • K {solarData.kIndex} • General conditions for all paths
) : ( - /* Bar Chart View */ -
- {/* Solar quick stats */} + <> + {/* MUF/LUF and Data Source Info */}
- SFI {solarData.sfi} - {ionospheric?.foF2 ? ( - foF2 {ionospheric.foF2} - ) : ( - SSN {solarData.ssn} - )} - K = 4 ? '#ff4444' : '#00ff88' }}>{solarData.kIndex} +
+ + MUF + {muf || '?'} + MHz + + + LUF + {luf || '?'} + MHz + +
+ + {hasRealData ? `📡 ${ionospheric?.source || 'ionosonde'}` : '⚡ estimated'} +
- {currentBands.slice(0, 8).map((band, idx) => ( -
- + {/* Heat map grid */} +
= 50 ? 'var(--accent-green)' : 'var(--text-muted)' + fontFamily: 'JetBrains Mono, monospace' }}> - {band.band} - -
-
+ {bands.map((band, bandIdx) => ( + + {/* Band label */} +
+ {band.replace('m', '')} +
+ {/* 24 hour cells */} + {Array.from({ length: 24 }, (_, hour) => { + const bandData = hourlyPredictions?.[band]; + const hourData = bandData?.find(h => h.hour === hour); + const rel = hourData?.reliability || 0; + return ( +
+ ); + })} + + ))}
- - {band.reliability}% - +
UTC
+ {[0, '', '', 3, '', '', 6, '', '', 9, '', '', 12, '', '', 15, '', '', 18, '', '', 21, '', ''].map((h, i) => ( +
{h}
+ ))} +
+ + {/* Legend & Info */} +
+
+ REL: +
+
+
+
+
+
+
+
+ {Math.round(distance)}km • {ionospheric?.foF2 ? `foF2=${ionospheric.foF2}` : `SSN=${solarData.ssn}`} +
+
- ))} -
+ ) : ( + /* Bar Chart View */ +
+ {/* Solar quick stats */} +
+ SFI {solarData.sfi} + {ionospheric?.foF2 ? ( + foF2 {ionospheric.foF2} + ) : ( + SSN {solarData.ssn} + )} + K = 4 ? '#ff4444' : '#00ff88' }}>{solarData.kIndex} +
+ + {currentBands.slice(0, 8).map((band, idx) => ( +
+ = 50 ? 'var(--accent-green)' : 'var(--text-muted)' + }}> + {band.band} + +
+
+
+ + {band.reliability}% + +
+ ))} +
+ )} + )}
); @@ -2404,30 +2455,6 @@ ); }; - const BandConditionsPanel = ({ bands, loading }) => { - const getStyle = (c) => ({ GOOD: { bg: 'rgba(0,255,136,0.15)', color: 'var(--accent-green)', border: 'rgba(0,255,136,0.3)' }, FAIR: { bg: 'rgba(255,180,50,0.15)', color: 'var(--accent-amber)', border: 'rgba(255,180,50,0.3)' }, POOR: { bg: 'rgba(255,68,102,0.15)', color: 'var(--accent-red)', border: 'rgba(255,68,102,0.3)' } }[c] || { bg: 'rgba(255,180,50,0.15)', color: 'var(--accent-amber)', border: 'rgba(255,180,50,0.3)' }); - - return ( -
-
- 📡 BAND CONDITIONS - {loading &&
} -
-
- {bands.map((b, i) => { - const s = getStyle(b.condition); - return ( -
-
{b.band}
-
{b.condition}
-
- ); - })} -
-
- ); - }; - const DXClusterPanel = ({ spots, loading, activeSource, showOnMap, onToggleMap }) => (
@@ -3722,37 +3749,11 @@
- {/* Band Conditions - Compact with color coding */} -
-
📊 BAND CONDITIONS
-
- {bandConditions.data.slice(0, 12).map(band => { - const colors = { - GOOD: { bg: 'rgba(0,255,136,0.2)', color: '#00ff88', border: 'rgba(0,255,136,0.4)' }, - FAIR: { bg: 'rgba(255,180,50,0.2)', color: '#ffb432', border: 'rgba(255,180,50,0.4)' }, - POOR: { bg: 'rgba(255,68,102,0.2)', color: '#ff4466', border: 'rgba(255,68,102,0.4)' } - }; - const style = colors[band.condition] || colors.FAIR; - return ( -
-
{band.band}
-
- ); - })} -
-
- {/* Solar Panel (toggleable between image and indices) */} - {/* VOACAP Propagation - Toggleable */} - + {/* VOACAP/Propagation/Band Conditions - Toggleable */} +
{/* CENTER - MAP */}