/* organizer-pages.jsx — All page-section components for the Arena Organizer * React 18 · in-browser Babel · no build step * Data comes from props, NOT from window.ORG_* globals * Exports all components on window.* */ // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function openPicker(e) { try { if (e.currentTarget && typeof e.currentTarget.showPicker === 'function') e.currentTarget.showPicker(); } catch (_) {} } function formatTs(ts) { if (!ts) return '—'; return new Date(ts * 1000).toLocaleDateString(); } function PhaseBadge(props) { var phase = props.phase || ''; var colorMap = { registration: 'brand', draft: 'wn', playing: 'ok' }; var cls = 'pill ' + (colorMap[phase] || ''); return React.createElement('span', { className: cls }, phase || '—'); } // --------------------------------------------------------------------------- // OrgSessionsDashboard // --------------------------------------------------------------------------- window.OrgSessionsDashboard = function OrgSessionsDashboard(props) { var games = props.games || []; var metrics = props.metrics || null; var onCreateGame = props.onCreateGame || function() {}; var onNavigate = props.onNavigate || function() {}; var onEditGame = props.onEditGame || function() {}; var onDeleteGame = props.onDeleteGame || function() {}; var onOpenMonitor = props.onOpenMonitor || function() {}; var onRefresh = props.onRefresh || function() {}; var loading = props.loading || false; var totalGames = metrics ? metrics.total_games : '—'; var totalPlayers = metrics ? metrics.total_players : '—'; var avgPerGame = metrics ? metrics.avg_players_per_game : '—'; var scoreAvg = metrics && metrics.score_stats ? metrics.score_stats.avg : '—'; return (
{/* Page header */}
Sessions {games.length} game{games.length !== 1 ? 's' : ''}
{/* KPI strip */}
Total games {totalGames}
Total players {totalPlayers}
Avg per game {avgPerGame}
Score avg {scoreAvg}
{/* Games table */}

All games

{games.length === 0 ? (
🎮
No sessions yet
Click '+ New session' to create one.
) : (
{games.map(function(game) { return (
{game.join_code || game.game_id} 📋
{game.name || '—'}
{game.location && (
{game.location}
)}
{formatTs(game.created_at)}
{game.is_event_draft && ( )} {game.event_id && !game.is_event_draft && ( )} {!game.is_event_draft && ( )}
); })}
)}
); }; // --------------------------------------------------------------------------- // OrgOverviewPage // --------------------------------------------------------------------------- window.OrgOverviewPage = function OrgOverviewPage(props) { var metrics = props.metrics || null; var onRefresh = props.onRefresh || function() {}; return (
{/* Page header */}
Overview
{metrics === null ? (
Loading metrics…
) : ( <> {/* Primary KPIs */}
Total games {metrics.total_games != null ? metrics.total_games : '—'}
Total players {metrics.total_players != null ? metrics.total_players : '—'}
Avg players/game {metrics.avg_players_per_game != null ? metrics.avg_players_per_game : '—'}
Events count {metrics.events_count != null ? metrics.events_count : '—'}
{/* Score stats card */} {metrics.score_stats && (

Score stats

Min {metrics.score_stats.min != null ? metrics.score_stats.min : '—'}
Max {metrics.score_stats.max != null ? metrics.score_stats.max : '—'}
Avg {metrics.score_stats.avg != null ? metrics.score_stats.avg : '—'}
)} {/* Module popularity */} {metrics.module_popularity && metrics.module_popularity.length > 0 && (

Module popularity

{metrics.module_popularity.map(function(mod, i) { return (
{mod.name || mod.module || mod} {mod.count || mod.plays || ''}
); })}
)}
{/* Recent games */} {metrics.recent_games && metrics.recent_games.length > 0 && (

Recent games

{metrics.recent_games.slice(0, 5).map(function(game, i) { return (
{game.game_id} {game.name || '—'}
); })}
)} )}
); }; // --------------------------------------------------------------------------- // MonitorOpsTabs — tab strip for OrgMonitorPage // --------------------------------------------------------------------------- window.MonitorOpsTabs = function MonitorOpsTabs(props) { var activeTab = props.activeTab || 'control'; var onChange = props.onChange || function() {}; var badges = props.badges || {}; var TABS = [ { id: 'control', label: 'Control', icon: '🎛️' }, { id: 'breach', label: 'Breach VAR', icon: '🔓' }, { id: 'gameplan', label: 'GamePlan', icon: '📐' }, { id: 'zonamixta', label: 'Zona Mixta', icon: '⚡' }, { id: 'triage', label: 'Triage Review', icon: '🎫' }, ]; return (
{TABS.map(function(tab) { var isActive = activeTab === tab.id; var badge = badges[tab.id]; return ( ); })}
); }; // --------------------------------------------------------------------------- // GamePlanScoringTab // --------------------------------------------------------------------------- window.GamePlanScoringTab = function GamePlanScoringTab(props) { var gameId = props.gameId; var _s1 = React.useState([]), submissions = _s1[0], setSubmissions = _s1[1]; var _s2 = React.useState(null), selectedTeamId = _s2[0], setSelectedTeamId = _s2[1]; var _s3 = React.useState(false), loading = _s3[0], setLoading = _s3[1]; var _s4 = React.useState(null), saveError = _s4[0], setSaveError = _s4[1]; var _s5 = React.useState(false), saving = _s5[0], setSaving = _s5[1]; var _s6 = React.useState(''), saveMsg = _s6[0], setSaveMsg = _s6[1]; // Rubric scores var _r1 = React.useState(0), technical = _r1[0], setTechnical = _r1[1]; var _r2 = React.useState(0), bom = _r2[0], setBom = _r2[1]; var _r3 = React.useState(0), clarity = _r3[0], setClarity = _r3[1]; var _r4 = React.useState(0), presentation = _r4[0], setPresentation = _r4[1]; var _rn = React.useState(''), notes = _rn[0], setNotes = _rn[1]; var total = technical + bom + clarity + presentation; var RUBRIC = [ { key: 'technical', label: 'Technical correctness / fit', max: 200, bands: [60, 120, 170, 200], val: technical, set: setTechnical }, { key: 'bom', label: 'BOM accuracy / completeness', max: 125, bands: [40, 80, 110, 125], val: bom, set: setBom }, { key: 'clarity', label: 'Architecture clarity', max: 125, bands: [40, 80, 110, 125], val: clarity, set: setClarity }, { key: 'presentation', label: 'Presentation / executive delivery', max: 50, bands: [10, 25, 40, 50], val: presentation, set: setPresentation }, ]; function _authHeaders() { var t = (window.AdminSession && window.AdminSession.getToken) ? window.AdminSession.getToken() : null; return t ? { 'Authorization': 'Bearer ' + t } : {}; } function fetchSubmissions() { if (!gameId) return; setLoading(true); fetch('/api/g/' + encodeURIComponent(gameId) + '/proctor/gameplan-submissions', { headers: _authHeaders() }) .then(function(r) { return r.ok ? r.json() : Promise.reject(r); }) .then(function(data) { var teams = data.teams || []; setSubmissions(teams); // Use functional setter to read CURRENT selectedTeamId (avoid stale closure from 10s interval) setSelectedTeamId(function(prev) { if (prev) return prev; return teams.length > 0 ? teams[0].team_id : null; }); }) .catch(function(err) { console.error('[GamePlanScoring] fetch failed:', err); }) .finally(function() { setLoading(false); }); } React.useEffect(function() { fetchSubmissions(); var interval = setInterval(fetchSubmissions, 10000); return function() { clearInterval(interval); }; }, [gameId]); // Load existing score when team changes React.useEffect(function() { if (!selectedTeamId) return; var team = submissions.find(function(t) { return t.team_id === selectedTeamId; }); if (team && team.score) { setTechnical(team.score.technical || 0); setBom(team.score.bom || 0); setClarity(team.score.clarity || 0); setPresentation(team.score.presentation || 0); setNotes(team.notes || ''); } else { setTechnical(0); setBom(0); setClarity(0); setPresentation(0); setNotes(''); } }, [selectedTeamId]); // Keyboard shortcuts React.useEffect(function() { function onKey(e) { if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') return; var idx = submissions.findIndex(function(t) { return t.team_id === selectedTeamId; }); if (e.key === 'j' || e.key === 'J') { var next = submissions[idx + 1]; if (next) { setSelectedTeamId(next.team_id); } } else if (e.key === 'k' || e.key === 'K') { var prev = submissions[idx - 1]; if (prev) { setSelectedTeamId(prev.team_id); } } else if (e.key === 'Enter') { handleSave(); } } window.addEventListener('keydown', onKey); return function() { window.removeEventListener('keydown', onKey); }; }, [selectedTeamId, submissions, technical, bom, clarity, presentation, notes]); function handleSave() { if (!selectedTeamId) return; setSaving(true); setSaveError(null); fetch('/api/g/' + encodeURIComponent(gameId) + '/proctor/gameplan-score', { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json' }, _authHeaders()), body: JSON.stringify({ team_id: selectedTeamId, total: total, breakdown: { technical: technical, bom: bom, clarity: clarity, presentation: presentation }, notes: notes, }), }) .then(function(r) { return r.ok ? r.json() : Promise.reject(r); }) .then(function() { setSaveMsg('✅ Saved!'); setTimeout(function() { setSaveMsg(''); }, 3500); fetchSubmissions(); // Advance to next team var idx = submissions.findIndex(function(t) { return t.team_id === selectedTeamId; }); var next = submissions[idx + 1]; if (next) setSelectedTeamId(next.team_id); }) .catch(function(err) { console.error('[GamePlanScoring] save failed:', err); if (err && err.json) { err.json().then(function(j) { setSaveError('Save failed: ' + (j.detail || JSON.stringify(j))); }).catch(function(){ setSaveError('Save failed (HTTP ' + (err.status || '?') + ')'); }); } else { setSaveError('Save failed'); } }) .finally(function() { setSaving(false); }); } var selectedTeam = submissions.find(function(t) { return t.team_id === selectedTeamId; }); // Pull scenario brief from window.GAME_MODULES['ise'].data.briefs (client-side reference) function _getBrief(idx) { if (idx == null) return null; try { var mods = (typeof window !== 'undefined' && window.GAME_MODULES) ? window.GAME_MODULES : null; if (!mods) return null; // Try ise first (live game), then fall back to any module that has briefs var mod = mods['ise']; if (!mod) { for (var k in mods) { if (mods[k] && mods[k].data && mods[k].data.briefs) { mod = mods[k]; break; } } } var briefs = (mod && mod.data && mod.data.briefs) || null; if (briefs && briefs[idx]) return briefs[idx]; } catch(e) {} return null; } var brief = selectedTeam ? _getBrief(selectedTeam.scenario_index) : null; // Local helpers for compact styled blocks var _LBL = { fontSize: '11px', color: 'var(--i3)', textTransform: 'uppercase', letterSpacing: '.05em', marginBottom: '4px', fontWeight: 600 }; var _PILL = { display: 'inline-block', padding: '2px 8px', borderRadius: '10px', fontSize: '11px', fontWeight: 600, marginRight: '6px', marginBottom: '4px' }; return (
{/* Left rail — team list */}

Teams

{loading && submissions.length === 0 && (
Loading…
)} {submissions.map(function(team) { var isActive = team.team_id === selectedTeamId; var hasSubmission = !!team.submission; var isScored = team.score && (team.score.total > 0 || (team.score.breakdown && (team.score.breakdown.technical > 0 || team.score.breakdown.bom > 0))); var statusIcon = !hasSubmission ? '❌' : (isScored ? '✅' : '⏳'); return (
{statusIcon} {team.team_name || team.team_id}
); })} {submissions.length === 0 && !loading && (
No submissions yet
)}
{/* Center — submission */}

{selectedTeam ? (selectedTeam.team_name || selectedTeam.team_id) : 'Select a team'}

{!selectedTeam ? (
Select a team from the left rail.
) : !selectedTeam.submission ? (
❌ No submission received yet.
) : (
{/* Scenario brief reference (proctor-only) */} {selectedTeam.scenario_index != null && (
{/* Header */}
Escenario #{selectedTeam.scenario_index + 1}{brief && brief.phase ? ' · ' + brief.phase : ''}
{brief && brief.logo ? brief.logo + ' ' : '🏢 '} {brief ? (brief.client || 'Cliente') : 'Brief no disponible — recarga la página'}
{brief && (
{brief.industry && {brief.industry}} {brief.employees && · {brief.employees.toLocaleString ? brief.employees.toLocaleString() : brief.employees} empleados} {brief.endpoints && · {brief.endpoints.toLocaleString ? brief.endpoints.toLocaleString() : brief.endpoints} endpoints} {brief.sites && · {brief.sites} sitios} {brief.budget && · {brief.budget}}
)}
{brief && (
{/* TRICK REQUIREMENT — most important for scoring */} {brief.trickRequirement && (
⚠️ Requerimiento trampa (deduce si lo ignoraron)
{brief.trickRequirement}
)} {/* Context */} {brief.context && (
📋 Contexto del cliente
{brief.context}
)} {/* Pain points + constraints */} {(brief.painPoints || brief.constraints) && (
🎯 Pain points & restricciones
{brief.painPoints && (
Pain points
    {brief.painPoints.map(function(p, i) { return
  • {p}
  • ; })}
)} {brief.constraints && (
Restricciones
    {brief.constraints.map(function(p, i) { return
  • {p}
  • ; })}
)}
)} {/* Licensing */} {brief.licensingRequirements && (
🎟️ Licenciamiento esperado
{brief.licensingRequirements.tier && {brief.licensingRequirements.tier}} {brief.licensingRequirements.endpointCount && {brief.licensingRequirements.endpointCount.toLocaleString ? brief.licensingRequirements.endpointCount.toLocaleString() : brief.licensingRequirements.endpointCount} endpoints} {brief.licensingRequirements.subscriptionTerm && {brief.licensingRequirements.subscriptionTerm}} {brief.licensingRequirements.deviceAdmin && Device Admin}
{brief.licensingRequirements.features && brief.licensingRequirements.features.length > 0 && (
Features: {brief.licensingRequirements.features.join(', ')}
)}
)} {/* Gold-standard BOM */} {brief.goldStandardBom && (
🏆 BOM gold standard (referencia)
{brief.goldStandardBom.iseNodes && brief.goldStandardBom.iseNodes.length > 0 && (
Nodos ISE
    {brief.goldStandardBom.iseNodes.map(function(n, i) { var txt = (typeof n === 'string') ? n : (n.qty ? n.qty + '× ' : '') + (n.model || '') + (n.role ? ' (' + n.role + ')' : '') + (n.notes ? ' — ' + n.notes : ''); return
  • {txt}
  • ; })}
)} {brief.goldStandardBom.licensing && brief.goldStandardBom.licensing.length > 0 && (
Licencias
    {brief.goldStandardBom.licensing.map(function(l, i) { var txt = (typeof l === 'string') ? l : (l.sku ? l.sku + ' ' : '') + (l.qty ? '×' + l.qty + ' ' : '') + (l.description || l.notes || ''); return
  • {txt}
  • ; })}
)} {brief.goldStandardBom.integration && brief.goldStandardBom.integration.length > 0 && (
Integraciones esperadas
    {brief.goldStandardBom.integration.map(function(it, i) { return
  • {typeof it === 'string' ? it : (it.name || JSON.stringify(it))}
  • ; })}
)} {brief.goldStandardBom.commonMistakes && brief.goldStandardBom.commonMistakes.length > 0 && (
❌ Errores comunes — buscar en la entrega
    {brief.goldStandardBom.commonMistakes.map(function(m, i) { return
  • {m}
  • ; })}
)}
)} {/* BOM focus hint */} {brief.bomFocus && (
Foco BOM: {brief.bomFocus}
)}
)}
)} {/* BOM file */} {selectedTeam.submission.bom_file && (
BOM File
📄 {selectedTeam.submission.bom_file}
)} {/* Architecture image */} {selectedTeam.submission.arch_file && /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(selectedTeam.submission.arch_file) && (
Architecture Diagram
Architecture diagram
)} {/* Non-image arch file */} {selectedTeam.submission.arch_file && !/\.(png|jpg|jpeg|gif|webp|svg)$/i.test(selectedTeam.submission.arch_file) && (
Architecture File
📎 {selectedTeam.submission.arch_file}
)} {/* Free-text / notes from team */} {selectedTeam.submission.notes && (
Team Notes
{selectedTeam.submission.notes}
)}
)}
{/* Right rail — rubric */}

Rubric

{RUBRIC.map(function(dim) { return (
{dim.label} 0 ? '#00BCEB' : 'var(--i3)' }}> {dim.val}/{dim.max}
{dim.bands.map(function(band) { var isSelected = dim.val === band; return ( ); })}
); })} {/* Total */}
Total {total} / 500
{/* Notes */}
Notes