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 (
);
}
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 */}
{/* 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;