/** * Staff & Organizer Command Center Component */ import { escapeHTML, setHTML } from '../utils/dom.js'; import { Toast } from './Toast.js'; import { speak } from '../utils/tts.js'; export class StaffPanel { constructor(containerEl, appInstance) { this.containerEl = containerEl; this.appInstance = appInstance; this.activeTab = 'organizer'; this.incidents = [ { id: 1, type: 'Medical Aid', location: 'Section 102', status: 'Pending', time: '12:45' }, { id: 2, type: 'Spill / Cleaning', location: 'Concession Floor 3', status: 'In Progress', time: '12:48' }, { id: 3, type: 'Gate Flow Alert', location: 'Gate 4 Turnstiles', status: 'Resolved', time: '12:30' } ]; this.phrases = [ { id: 'ticket', text: 'Please show me your digital match day pass.' }, { id: 'gate', text: 'The closest entrance to your seat is Gate 7 (step-free).' }, { id: 'medical', text: 'First-aid station is located on Level 1, near Section 105.' }, { id: 'sensory', text: 'The sensory calm room is on Level 3, room 302.' } ]; this.selectedLanguage = 'es'; this.selectedPhrase = 'ticket'; this.translatedText = 'Por favor, muestre su pase digital para el día del partido.'; this.isTranslating = false; this.adviceText = 'AI recommendation: Re-route Gate 4 shuttle arrivals to Gate 7 step-free route to balance flow and prevent queue building.'; this.isAnalyzing = false; } render() { if (!this.containerEl) return; setHTML(this.containerEl, `

🏟️ Staff Command Center

OPERATOR MODE
${this._renderTabs()} ${this.activeTab === 'organizer' ? this._renderOrganizerView() : this._renderVolunteerView()}
`); this.bindEvents(); } _renderTabs() { return `
`; } _renderOrganizerView() { const adviceText = this.isAnalyzing ? 'Running GenAI flow optimization...' : this.adviceText; return `
ACTIVE GATE CONGESTION
MEDIUM
AVG QUEUE TIME
8 MINS
GenAI Crowd Flow Advisor
${escapeHTML(adviceText)}
`; } _renderVolunteerView() { return `
Active Incidents & Dispatch
${this.incidents.map(incident => this._renderIncident(incident)).join('')}
International Fan Assistant
${escapeHTML(this.isTranslating ? 'Translating...' : this.translatedText)}
`; } _renderIncident(incident) { const statusHtml = incident.status === 'Resolved' ? 'RESOLVED' : ``; return `
${escapeHTML(incident.type)} ${escapeHTML(incident.location)}
Reported: ${escapeHTML(incident.time)}
${statusHtml}
`; } _renderPhraseOption(phrase) { return ``; } _renderLanguageOptions() { const languages = [ ['es', 'Spanish'], ['fr', 'French'], ['de', 'German'], ['hi', 'Hindi'], ['pt', 'Portuguese'], ['zh', 'Chinese'], ['ja', 'Japanese'], ]; return languages .map(([value, label]) => ``) .join(''); } bindEvents() { this.containerEl.querySelector('#staff-tab-org')?.addEventListener('click', () => { this.activeTab = 'organizer'; this.render(); }); this.containerEl.querySelector('#staff-tab-vol')?.addEventListener('click', () => { this.activeTab = 'volunteer'; this.render(); }); this.containerEl.querySelector('#btn-refresh-ai-advice')?.addEventListener('click', async (e) => { const button = e.currentTarget; button.disabled = true; button.textContent = 'Analyzing crowd patterns...'; this.isAnalyzing = true; const adviceBox = this.containerEl.querySelector('#staff-ai-advice'); if (adviceBox) { adviceBox.textContent = 'Running GenAI flow optimization...'; adviceBox.classList.add('animate-pulse'); } try { const response = await fetch('/api/translate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Queue management action: Open auxiliary turnstiles at Gate 7 to accommodate heavy inbound flows. Deploy Volunteer dispatch squad.', targetLanguage: 'en' }) }); const adviceText = response.ok ? (await response.json()).translatedText : 'GenAI suggestion: Deploy group volunteers to redirect the crowd coming from Section 102 towards the North Metro Hub.'; this.adviceText = adviceText; if (adviceBox) adviceBox.textContent = this.adviceText; Toast.show({ message: 'GenAI Flow Optimization complete!', type: 'success', duration: 3000 }); } catch { this.adviceText = 'GenAI suggestion: Deploy group volunteers to redirect the crowd coming from Section 102 towards the North Metro Hub.'; if (adviceBox) adviceBox.textContent = this.adviceText; } finally { this.isAnalyzing = false; if (adviceBox) adviceBox.classList.remove('animate-pulse'); button.disabled = false; button.textContent = 'Run GenAI Flow Analysis'; } }); this.containerEl.querySelectorAll('.btn-resolve-incident').forEach(btn => { btn.addEventListener('click', () => { const id = parseInt(btn.dataset.incId, 10); const incident = this.incidents.find(inc => inc.id === id); if (incident) { incident.status = 'Resolved'; Toast.show({ message: `Incident resolved: ${incident.type}`, type: 'success', duration: 2500 }); this.render(); } }); }); const querySelect = this.containerEl.querySelector('#staff-query-select'); const langSelect = this.containerEl.querySelector('#staff-lang-select'); const handleTranslation = async () => { if (!querySelect || !langSelect) return; const phraseId = querySelect.value; const lang = langSelect.value; const phrase = this.phrases.find(p => p.id === phraseId); if (!phrase) return; this.selectedPhrase = phraseId; this.selectedLanguage = lang; this.isTranslating = true; const translatedBox = this.containerEl.querySelector('#staff-translated-box'); if (translatedBox) { translatedBox.textContent = 'Translating...'; translatedBox.classList.add('animate-pulse'); } try { const response = await fetch('/api/translate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: phrase.text, targetLanguage: lang }) }); if (response.ok) { const res = await response.json(); this.translatedText = res.translatedText; } else { this.translatedText = phrase.text; // fallback } } catch { this.translatedText = phrase.text; } finally { this.isTranslating = false; if (translatedBox) { translatedBox.textContent = this.translatedText; translatedBox.classList.remove('animate-pulse'); } } }; querySelect?.addEventListener('change', handleTranslation); langSelect?.addEventListener('change', handleTranslation); this.containerEl.querySelector('#btn-speak-translation')?.addEventListener('click', () => { if (this.translatedText) { speak(this.translatedText, { lang: this.selectedLanguage }); Toast.show({ message: `Speaking translation aloud...`, type: 'info', duration: 2000 }); } }); } }