import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import api from '../api/axios'; import GamePoolModal from '../components/GamePoolModal'; import { formatLocalTime } from '../utils/dateUtils'; function Picker() { const { isAuthenticated, loading: authLoading } = useAuth(); const navigate = useNavigate(); const [activeSession, setActiveSession] = useState(null); const [allGames, setAllGames] = useState([]); const [selectedGame, setSelectedGame] = useState(null); const [loading, setLoading] = useState(true); const [picking, setPicking] = useState(false); const [error, setError] = useState(''); // Filters const [playerCount, setPlayerCount] = useState(''); const [drawingFilter, setDrawingFilter] = useState('both'); const [lengthFilter, setLengthFilter] = useState(''); const [familyFriendlyFilter, setFamilyFriendlyFilter] = useState(''); // Manual game selection const [showManualSelect, setShowManualSelect] = useState(false); const [manualGameId, setManualGameId] = useState(''); // Game pool viewer const [showGamePool, setShowGamePool] = useState(false); const [eligibleGames, setEligibleGames] = useState([]); // Trigger to refresh session games list const [gamesUpdateTrigger, setGamesUpdateTrigger] = useState(0); // Mobile filters toggle const [showFilters, setShowFilters] = useState(false); // Exclude previously played games const [excludePlayedGames, setExcludePlayedGames] = useState(false); useEffect(() => { // Wait for auth to finish loading before checking authentication if (authLoading) return; if (!isAuthenticated) { navigate('/login'); return; } loadData(); }, [isAuthenticated, authLoading, navigate]); const loadData = async () => { try { // Load active session or create one const sessionResponse = await api.get('/sessions/active'); // Handle new format { session: null } or old format (direct session object) let session = sessionResponse.data?.session !== undefined ? sessionResponse.data.session : sessionResponse.data; // If no active session, create one if (!session || !session.id) { const newSession = await api.post('/sessions', {}); session = newSession.data; } setActiveSession(session); // Load all games for manual selection const gamesResponse = await api.get('/games'); setAllGames(gamesResponse.data); } catch (err) { setError('Failed to load session data'); } finally { setLoading(false); } }; const loadEligibleGames = async () => { try { const params = new URLSearchParams(); params.append('enabled', 'true'); if (playerCount) { params.append('playerCount', playerCount); } if (drawingFilter !== 'both') { params.append('drawing', drawingFilter); } if (lengthFilter) { params.append('length', lengthFilter); } if (familyFriendlyFilter) { params.append('familyFriendly', familyFriendlyFilter); } let games = await api.get(`/games?${params.toString()}`); let eligibleGamesList = games.data; // Apply session-based exclusions if needed if (activeSession && excludePlayedGames) { // Get all played games in this session const sessionGamesResponse = await api.get(`/sessions/${activeSession.id}/games`); const playedGameIds = sessionGamesResponse.data.map(g => g.game_id); // Filter out played games eligibleGamesList = eligibleGamesList.filter(game => !playedGameIds.includes(game.id)); } else if (activeSession) { // Default behavior: exclude last 2 games const sessionGamesResponse = await api.get(`/sessions/${activeSession.id}/games`); const recentGames = sessionGamesResponse.data.slice(-2); const recentGameIds = recentGames.map(g => g.game_id); eligibleGamesList = eligibleGamesList.filter(game => !recentGameIds.includes(game.id)); } setEligibleGames(eligibleGamesList); } catch (err) { console.error('Failed to load eligible games', err); } }; const handlePickGame = async () => { if (!activeSession) return; setPicking(true); setError(''); try { const response = await api.post('/pick', { sessionId: activeSession.id, playerCount: playerCount ? parseInt(playerCount) : undefined, drawing: drawingFilter !== 'both' ? drawingFilter : undefined, length: lengthFilter || undefined, familyFriendly: familyFriendlyFilter ? familyFriendlyFilter === 'yes' : undefined, excludePlayed: excludePlayedGames }); setSelectedGame(response.data.game); } catch (err) { setError(err.response?.data?.error || 'Failed to pick a game'); setSelectedGame(null); } finally { setPicking(false); } }; const handleAcceptGame = async () => { if (!selectedGame || !activeSession) return; try { await api.post(`/sessions/${activeSession.id}/games`, { game_id: selectedGame.id, manually_added: false }); // Trigger games list refresh setGamesUpdateTrigger(prev => prev + 1); setSelectedGame(null); setError(''); } catch (err) { setError('Failed to add game to session'); } }; const handleAddManualGame = async () => { if (!manualGameId || !activeSession) return; try { await api.post(`/sessions/${activeSession.id}/games`, { game_id: parseInt(manualGameId), manually_added: true }); // Trigger games list refresh setGamesUpdateTrigger(prev => prev + 1); setManualGameId(''); setShowManualSelect(false); setError(''); } catch (err) { setError('Failed to add game to session'); } }; const handleSelectVersion = async (gameId) => { if (!activeSession) return; try { await api.post(`/sessions/${activeSession.id}/games`, { game_id: gameId, manually_added: false }); // Trigger games list refresh setGamesUpdateTrigger(prev => prev + 1); setSelectedGame(null); setError(''); } catch (err) { setError('Failed to add game to session'); } }; // Find similar versions of a game based on title patterns const findSimilarVersions = (game) => { if (!game) return []; // Extract base name by removing common version patterns const baseName = game.title .replace(/\s*\d+$/, '') // Remove trailing numbers (e.g., "Game 2" -> "Game") .replace(/\s*:\s*.*$/, '') // Remove subtitle after colon .replace(/\s*\(.*?\)$/, '') // Remove parenthetical .trim(); // Find games with similar base names (but not the exact same game) return allGames.filter(g => { if (g.id === game.id) return false; // Exclude the current game if (!g.enabled) return false; // Only show enabled games const otherBaseName = g.title .replace(/\s*\d+$/, '') .replace(/\s*:\s*.*$/, '') .replace(/\s*\(.*?\)$/, '') .trim(); // Match if base names are the same (case insensitive) return otherBaseName.toLowerCase() === baseName.toLowerCase(); }); }; const similarVersions = React.useMemo(() => { return findSimilarVersions(selectedGame); }, [selectedGame, allGames]); if (authLoading || loading) { return (
Loading...
); } if (!activeSession) { return (
Failed to load or create session. Please try again.
); } return (

Game Picker

{/* Picker Controls Panel */}
{/* Main Action Buttons - Above filters on mobile */}
{/* Exclude played games checkbox */}
{/* Filters */}
{/* Mobile: Collapsible header */} {/* Desktop: Always show title */}

Filters

{/* Filter content - collapsible on mobile */}
setPlayerCount(e.target.value)} className="w-full px-3 py-2 pr-8 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 text-sm text-center [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" placeholder="Any" /> {playerCount && ( )}
{/* Compact Toggle Filters */}
{/* Game Pool Modal */} {showGamePool && ( setShowGamePool(false)} /> )} {/* Results Panel */}
{error && (
{error}
)} {selectedGame && (

{selectedGame.title}

{selectedGame.pack_name}

Players: {selectedGame.min_players}-{selectedGame.max_players}
Length: {selectedGame.length_minutes ? `${selectedGame.length_minutes} min` : 'Unknown'}
Type: {selectedGame.game_type || 'N/A'}
Family Friendly: {selectedGame.family_friendly ? 'Yes' : 'No'}
Play Count: {selectedGame.play_count}
Popularity: {selectedGame.popularity_score > 0 ? '+' : ''} {selectedGame.popularity_score}
{/* Other Versions Suggestion */} {similarVersions.length > 0 && (

🔄 Other Versions Available

This game has multiple versions. You can choose a different one:

{similarVersions.map((version) => ( ))}
)}
)} {showManualSelect && (

Manual Game Selection

)} {/* Session info and games */}
); } function SessionInfo({ sessionId, onGamesUpdate }) { const { isAuthenticated } = useAuth(); const [games, setGames] = useState([]); const [loading, setLoading] = useState(true); const [confirmingRemove, setConfirmingRemove] = useState(null); useEffect(() => { loadGames(); }, [sessionId, onGamesUpdate]); const loadGames = async () => { try { const response = await api.get(`/sessions/${sessionId}/games`); // Reverse chronological order (most recent first) setGames(response.data.reverse()); } catch (err) { console.error('Failed to load session games'); } finally { setLoading(false); } }; const handleUpdateStatus = async (gameId, newStatus) => { try { await api.patch(`/sessions/${sessionId}/games/${gameId}/status`, { status: newStatus }); loadGames(); // Reload to get updated statuses } catch (err) { console.error('Failed to update game status', err); } }; const handleRemoveClick = (gameId) => { if (confirmingRemove === gameId) { // Second click - actually remove handleRemoveGame(gameId); } else { // First click - show confirmation setConfirmingRemove(gameId); // Reset after 3 seconds setTimeout(() => setConfirmingRemove(null), 3000); } }; const handleRemoveGame = async (gameId) => { try { await api.delete(`/sessions/${sessionId}/games/${gameId}`); setConfirmingRemove(null); loadGames(); // Reload after deletion } catch (err) { console.error('Failed to remove game', err); setConfirmingRemove(null); } }; const getStatusBadge = (status) => { if (status === 'playing') { return ( 🎮 Playing ); } if (status === 'skipped') { return ( ⏭️ Skipped ); } return null; }; return (

Games Played This Session ({games.length})

{loading ? (

Loading...

) : games.length === 0 ? (

No games played yet. Pick a game to get started!

) : (
{games.map((game) => { const index = games.length - games.indexOf(game); return (
{index + 1}. {game.title} {getStatusBadge(game.status)} {game.manually_added === 1 && ( Manual )}
{game.pack_name} • {formatLocalTime(game.played_at)}
{/* Action buttons for admins */} {isAuthenticated && (
{game.status !== 'playing' && ( )} {game.status === 'playing' && ( )} {game.status !== 'skipped' && ( )}
)}
); })}
)}
); } export default Picker;