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'}
)
}
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...'}
>
)}
)
}