import { useState, useRef, useEffect, useCallback, useMemo } from 'react' import SlotMachine from './SlotMachine' import { THEMES, getRandomQuestion } from '../utils/wheelQuestions' import { startDrumRoll, stopDrumRoll, playDing } from '../utils/soundUtils' function formatPhone(num) { if (!num) return '' const cleaned = String(num).replace(/\s+/g, '') return cleaned.replace(/(\d{2})(?=\d)/g, '$1 ').trim() } const peopleToItems = (people) => people.map((p) => ({ id: p.id, label: p.merged_display_name || `${p.prenom} ${p.nom}`, isGuest: !!p.invite_par_prenom, person: p, })) const themeItems = THEMES.map((t) => ({ id: t.id, label: `${t.emoji} ${t.name}`, color: t.color, theme: t, })) function Timer({ duration = 90, onEnd }) { const [remaining, setRemaining] = useState(duration) const [running, setRunning] = useState(true) const intervalRef = useRef(null) const onEndRef = useRef(onEnd) onEndRef.current = onEnd useEffect(() => { setRemaining(duration) setRunning(true) let count = duration intervalRef.current = setInterval(() => { count-- setRemaining(count) if (count <= 0) { clearInterval(intervalRef.current) intervalRef.current = null setRunning(false) onEndRef.current?.() } }, 1000) return () => { if (intervalRef.current) clearInterval(intervalRef.current) } }, [duration]) useEffect(() => { return () => { if (intervalRef.current) clearInterval(intervalRef.current) } }, []) const mins = Math.floor(remaining / 60) const secs = remaining % 60 const pct = remaining / duration const color = pct > 0.5 ? 'text-green-400' : pct > 0.2 ? 'text-yellow-400' : 'text-red-400' return (
{String(mins).padStart(2, '0')}:{String(secs).padStart(2, '0')}
0.5 ? 'bg-green-400' : pct > 0.2 ? 'bg-yellow-400' : 'bg-red-400'}`} style={{ width: `${pct * 100}%` }} />
{running ? ( ) : remaining > 0 ? ( En pause ) : null}
) } function parsePhoto(path) { if (!path) return null if (path.startsWith('http')) return path if (path.startsWith('/uploads')) return path return `/uploads/${path.replace(/^\/?/, '')}` } export default function WheelFlow({ members, guests, onComplete }) { const allPeople = useMemo(() => [...members.filter((m) => !m.absent), ...guests], [members, guests]) const [phase, setPhase] = useState('theme-idle') const [themePhase, setThemePhase] = useState('idle') const [personPhase, setPersonPhase] = useState('idle') const [selectedTheme, setSelectedTheme] = useState(null) const [selectedPerson, setSelectedPerson] = useState(null) const [currentQuestion, setCurrentQuestion] = useState(null) const [questionCount, setQuestionCount] = useState(0) const usedQuestionsRef = useRef([]) const [remainingPeople, setRemainingPeople] = useState(() => peopleToItems(allPeople)) const remainingRef = useRef(remainingPeople) remainingRef.current = remainingPeople useEffect(() => { const onKey = (e) => { if (e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'PageUp' || e.key === 'ArrowLeft' || e.key === 'ArrowDown' || e.key === 'PageDown' || e.key === ' ') { e.preventDefault() e.stopPropagation() const target = document.querySelector('[class*="cursor-pointer"][class*="w-full"][class*="h-full"]') if (target) target.click() } } window.addEventListener('keydown', onKey, true) return () => window.removeEventListener('keydown', onKey, true) }, []) const [riggedPersonIdx, setRiggedPersonIdx] = useState(() => { const items = peopleToItems(allPeople) const idx = items.findIndex(p => p.person?.prenom?.toLowerCase() === 'estelle') return idx >= 0 ? idx : undefined }) useEffect(() => { const items = peopleToItems(allPeople) setRemainingPeople(items) remainingRef.current = items }, [allPeople]) useEffect(() => { if (remainingPeople.length === 0 && (phase === 'theme-idle' || phase === 'person-idle')) { setPhase('done') } }, [remainingPeople, phase]) const handleNextRound = useCallback(() => { setSelectedTheme(null) setSelectedPerson(null) setCurrentQuestion(null) setThemePhase('idle') setPersonPhase('idle') if (remainingRef.current.length === 0) { setPhase('done') } else { setPhase('theme-idle') } }, []) const resetRound = useCallback(() => { setRemainingPeople(peopleToItems(allPeople)) remainingRef.current = peopleToItems(allPeople) setSelectedTheme(null) setSelectedPerson(null) setCurrentQuestion(null) setThemePhase('idle') setPersonPhase('idle') setPhase('theme-idle') usedQuestionsRef.current = [] }, [allPeople]) const handleThemeSpinEnd = useCallback((item) => { setSelectedTheme(item.theme) setPhase('theme-result') stopDrumRoll() playDing() }, []) const handlePersonSpinEnd = useCallback((item) => { setSelectedPerson(item.person) setRiggedPersonIdx(undefined) const next = remainingRef.current.filter(p => p.id !== item.id) setRemainingPeople(next) remainingRef.current = next stopDrumRoll() playDing() let question = getRandomQuestion(selectedTheme?.id, usedQuestionsRef.current) if (!question) { const fallbackTheme = THEMES[Math.floor(Math.random() * THEMES.length)] question = getRandomQuestion(fallbackTheme.id, usedQuestionsRef.current) || { question: 'Racontez une anecdote sur votre parcours entrepreneurial.', questionIdx: 0, themeId: 'fallback', } } if (question) usedQuestionsRef.current.push(`${question.themeId}-${question.questionIdx}`) setCurrentQuestion(question) setPhase('person-result') }, [selectedTheme]) const handleSpinClick = (e) => { e.stopPropagation() const startSpin = (setMachinePhase) => { setMachinePhase('spinning') startDrumRoll() } if (phase === 'theme-idle') startSpin(setThemePhase) else if (phase === 'person-idle') startSpin(setPersonPhase) } const handleResultClick = useCallback((e) => { e?.stopPropagation() const nextPhase = phase === 'theme-result' ? 'person-idle' : 'question' if (phase === 'theme-result') { setThemePhase('idle') setPersonPhase('idle') } setPhase(nextPhase) if (nextPhase === 'question') setQuestionCount((c) => c + 1) }, [phase]) const handleTimerEnd = useCallback(() => { playDing() handleNextRound() }, [handleNextRound]) const themeWheelPhase = themePhase === 'spinning' ? 'spinning' : 'idle' const personWheelPhase = personPhase === 'spinning' ? 'spinning' : 'idle' if (phase === 'done') { return (
{ e.stopPropagation(); onComplete?.() }} >
{'\uD83C\uDF89'}

{'Tout le monde est pass\u00e9 !'}

{'Bravo \u00e0 tous les participants !'}

{'Cliquez pour continuer la pr\u00e9sentation'}

) } if (phase === 'question' && selectedTheme && selectedPerson) { const q = currentQuestion || { question: 'Pas de question disponible pour ce th\u00e8me.' } return (
{ e.stopPropagation(); handleNextRound() }} >
{selectedPerson.photo_url ? ( { e.target.style.display = 'none'; e.target.parentElement.textContent = selectedPerson.prenom[0] + selectedPerson.nom[0] }} /> ) : ( {selectedPerson.prenom[0]}{selectedPerson.nom[0]} )}

{selectedPerson.merged_display_name || `${selectedPerson.prenom} ${selectedPerson.nom}`}

{selectedPerson.societe || selectedPerson.activite || ''}

{selectedPerson.invite_par_prenom && (

Invité(e) par {selectedPerson.invite_par_prenom} {selectedPerson.invite_par_nom}

)}
{selectedPerson.telephone && {formatPhone(selectedPerson.telephone)}} {selectedPerson.email && {selectedPerson.email}}
Plus que {remainingPeople.length} participant{remainingPeople.length > 1 ? 's' : ''}
{selectedTheme?.emoji || '\uD83D\uDCCC'} {selectedTheme?.name || 'Question'}

{q.question}

) } if (phase === 'theme-result' && selectedTheme) { return (
{selectedTheme.emoji}

{selectedTheme.name}

Cliquez pour continuer

) } if (phase === 'person-result' && selectedPerson) { return (

La personne d{'\u00e9'}sign{'\u00e9'}e est...

{selectedPerson.merged_display_name || `${selectedPerson.prenom} ${selectedPerson.nom}`}

{selectedPerson.societe || selectedPerson.activite || ''}

{selectedPerson.invite_par_prenom && (

{'Invité(e) par'} {selectedPerson.invite_par_prenom} {selectedPerson.invite_par_nom}

)}
{selectedPerson.telephone && {'\uD83D\uDCDE'} {formatPhone(selectedPerson.telephone)}} {selectedPerson.email && {'\u2709'} {selectedPerson.email}}

{'Cliquez pour découvrir la question'}

) } return (
{phase.startsWith('theme') ? ( <>

{themePhase === 'idle' && "Cliquez pour lancer la machine \u00e0 sous des th\u00e8mes"} {themePhase === 'spinning' && 'S\u00e9lection en cours...'}

) : ( <>

{personPhase === 'idle' && `Cliquez pour lancer la machine (${remainingPeople.length} restant${remainingPeople.length > 1 ? 's' : ''})`} {personPhase === 'spinning' && 'S\u00e9lection en cours...'}

)}
) }