/** * ContestPanel Component * Displays upcoming and active contests with live indicators */ import React from 'react'; export const ContestPanel = ({ data, loading }) => { const getModeColor = (mode) => { switch(mode) { case 'CW': return 'var(--accent-cyan)'; case 'SSB': return 'var(--accent-amber)'; case 'RTTY': return 'var(--accent-purple)'; case 'FT8': case 'FT4': return 'var(--accent-green)'; case 'Mixed': return 'var(--text-secondary)'; default: return 'var(--text-secondary)'; } }; const formatDate = (dateStr) => { if (!dateStr) return ''; const date = new Date(dateStr); return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); }; const formatTime = (dateStr) => { if (!dateStr) return ''; const date = new Date(dateStr); return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }) + 'z'; }; // Check if contest is live (happening now) const isContestLive = (contest) => { if (!contest.start || !contest.end) return false; const now = new Date(); const start = new Date(contest.start); const end = new Date(contest.end); return now >= start && now <= end; }; // Check if contest starts within 24 hours const isStartingSoon = (contest) => { if (!contest.start) return false; const now = new Date(); const start = new Date(contest.start); const hoursUntil = (start - now) / (1000 * 60 * 60); return hoursUntil > 0 && hoursUntil <= 24; }; // Get time remaining or time until start const getTimeInfo = (contest) => { if (!contest.start || !contest.end) return formatDate(contest.start); const now = new Date(); const start = new Date(contest.start); const end = new Date(contest.end); if (now >= start && now <= end) { // Contest is live - show time remaining const hoursLeft = Math.floor((end - now) / (1000 * 60 * 60)); const minsLeft = Math.floor(((end - now) % (1000 * 60 * 60)) / (1000 * 60)); if (hoursLeft > 0) { return `${hoursLeft}h ${minsLeft}m left`; } return `${minsLeft}m left`; } else if (now < start) { // Contest hasn't started const hoursUntil = Math.floor((start - now) / (1000 * 60 * 60)); if (hoursUntil < 24) { return `Starts in ${hoursUntil}h`; } return formatDate(contest.start); } return formatDate(contest.start); }; // Sort contests: live first, then starting soon, then by date const sortedContests = data ? [...data].sort((a, b) => { const aLive = isContestLive(a); const bLive = isContestLive(b); const aSoon = isStartingSoon(a); const bSoon = isStartingSoon(b); if (aLive && !bLive) return -1; if (!aLive && bLive) return 1; if (aSoon && !bSoon) return -1; if (!aSoon && bSoon) return 1; return new Date(a.start) - new Date(b.start); }) : []; // Count live contests const liveCount = sortedContests.filter(isContestLive).length; return (
🏆 CONTESTS {liveCount > 0 && ( 🔴 {liveCount} LIVE )}
{loading ? (
) : sortedContests.length > 0 ? (
{sortedContests.slice(0, 4).map((contest, i) => { const live = isContestLive(contest); const soon = isStartingSoon(contest); return (
{live && ( )} {soon && !live && ( )} {contest.name}
{contest.mode} {getTimeInfo(contest)}
); })}
) : (
No upcoming contests
)}
{/* Contest Calendar Credit */}
WA7BNM Contest Calendar
); }; export default ContestPanel;