fixed navigation and updated buttons

This commit is contained in:
corey@blaishome.online
2025-03-18 23:42:30 -04:00
parent f3c86d7d11
commit 19e6336692
7 changed files with 963 additions and 391 deletions

View File

@@ -1,16 +1,14 @@
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo, useEffect } from 'react';
import { CssBaseline, Container, IconButton } from '@mui/material'; import { CssBaseline, Container, IconButton } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles'; // <-- Import createTheme import { ThemeProvider, createTheme } from '@mui/material/styles';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import { Brightness7, Brightness4 } from '@mui/icons-material';
import { ScoreProvider } from './context/ScoreContext'; // <-- Import ScoreProvider import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { ScoreProvider } from './context/ScoreContext';
import MainMenu from './components/MainMenu';
import GameSetup from './components/GameSetup'; import GameSetup from './components/GameSetup';
import ScoreTracker from './components/ScoreTracker'; import ScoreTracker from './components/ScoreTracker';
import GameSummary from './components/GameSummary';
import { Brightness7, Brightness4 } from '@mui/icons-material';
function App() { const App = () => {
// Theme state
const [mode, setMode] = useState(() => { const [mode, setMode] = useState(() => {
try { try {
const savedMode = localStorage.getItem('themeMode'); const savedMode = localStorage.getItem('themeMode');
@@ -42,6 +40,7 @@ function App() {
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<ScoreProvider> <ScoreProvider>
<BrowserRouter>
<CssBaseline /> <CssBaseline />
<Container <Container
maxWidth="lg" maxWidth="lg"
@@ -52,6 +51,7 @@ function App() {
pb: 4 pb: 4
}} }}
> >
{/* Theme Toggle Button */}
<IconButton <IconButton
onClick={toggleTheme} onClick={toggleTheme}
color="inherit" color="inherit"
@@ -59,19 +59,17 @@ function App() {
> >
{mode === 'dark' ? <Brightness7 /> : <Brightness4 />} {mode === 'dark' ? <Brightness7 /> : <Brightness4 />}
</IconButton> </IconButton>
<Router>
{/* Routes for navigation */}
<Routes> <Routes>
<Route path="/" element={<MainMenu />} /> {/* MainMenu as the default page */} <Route path="/" element={<GameSetup />} />
<Route path="/setup" element={<GameSetup />} /> <Route path="/score" element={<ScoreTracker />} />
<Route path="/score-tracker" element={<ScoreTracker />} />
<Route path="/summary" element={<GameSummary />} />
</Routes> </Routes>
</Router>
</Container> </Container>
</BrowserRouter>
</ScoreProvider> </ScoreProvider>
</ThemeProvider> </ThemeProvider>
); );
} };
export default App; export default App;

View File

@@ -1,30 +1,26 @@
// File: src/AppRouter.js // File: src/AppRouter.js
import React, { useState } from 'react'; import React from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
import { Container, IconButton } from '@mui/material'; import { Container, IconButton } from '@mui/material';
import { Brightness7, Brightness4 } from '@mui/icons-material'; import { Brightness7, Brightness4 } from '@mui/icons-material';
import GameSetup from './components/GameSetup'; import GameSetup from './components/GameSetup';
import ScoreTracker from './components/ScoreTracker'; import ScoreTracker from './components/ScoreTracker';
import GameSummary from './components/GameSummary'; import GameSummary from './components/GameSummary';
import { useTheme } from './context/ThemeContext'; import { useTheme } from './context/ThemeContext';
import { useScore } from './context/ScoreContext';
// The main router component
function AppRouter() { function AppRouter() {
const { mode, toggleTheme } = useTheme(); const { mode, toggleTheme } = useTheme();
const { state } = useScore();
const [gameStarted, setGameStarted] = useState(() => { // Check if a game is currently in progress
try { const isGameActive = () => {
const savedGame = localStorage.getItem('archeryScores'); return state.currentGame && state.currentGame.gameType !== null;
if (savedGame) { };
const parsedGame = JSON.parse(savedGame);
return parsedGame.currentGame && parsedGame.currentGame.gameType !== null;
}
} catch {
return false;
}
return false;
});
return ( return (
<Router>
<Container <Container
maxWidth="lg" maxWidth="lg"
sx={{ sx={{
@@ -41,20 +37,39 @@ function AppRouter() {
> >
{mode === 'dark' ? <Brightness7 /> : <Brightness4 />} {mode === 'dark' ? <Brightness7 /> : <Brightness4 />}
</IconButton> </IconButton>
<Router>
<Routes> <Routes>
{/* Home route - redirects to score if a game is active */}
<Route <Route
path="/" path="/"
element={ element={
!gameStarted isGameActive() ?
? <GameSetup onGameStart={() => setGameStarted(true)} /> <Navigate to="/score" replace /> :
: <ScoreTracker /> <GameSetup />
} }
/> />
<Route path="/summary" element={<GameSummary />} />
{/* Explicit score tracker route */}
<Route
path="/score"
element={
isGameActive() ?
<ScoreTracker /> :
<Navigate to="/" replace />
}
/>
{/* Summary route */}
<Route
path="/summary"
element={<GameSummary />}
/>
{/* Catch-all route for any invalid URLs */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes> </Routes>
</Router>
</Container> </Container>
</Router>
); );
} }

61
asc/src/AppRouter.old Normal file
View File

@@ -0,0 +1,61 @@
// File: src/AppRouter.js
import React, { useState } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import { Container, IconButton } from '@mui/material';
import { Brightness7, Brightness4 } from '@mui/icons-material';
import GameSetup from './components/GameSetup';
import ScoreTracker from './components/ScoreTracker';
import GameSummary from './components/GameSummary';
import { useTheme } from './context/ThemeContext';
function AppRouter() {
const { mode, toggleTheme } = useTheme();
const [gameStarted, setGameStarted] = useState(() => {
try {
const savedGame = localStorage.getItem('archeryScores');
if (savedGame) {
const parsedGame = JSON.parse(savedGame);
return parsedGame.currentGame && parsedGame.currentGame.gameType !== null;
}
} catch {
return false;
}
return false;
});
return (
<Container
maxWidth="lg"
sx={{
minHeight: '100vh',
bgcolor: 'background.default',
color: 'text.primary',
pb: 4
}}
>
<IconButton
onClick={toggleTheme}
color="inherit"
sx={{ position: 'absolute', top: 16, right: 16 }}
>
{mode === 'dark' ? <Brightness7 /> : <Brightness4 />}
</IconButton>
<Router>
<Routes>
<Route
path="/"
element={
!gameStarted
? <GameSetup onGameStart={() => setGameStarted(true)} />
: <ScoreTracker />
}
/>
<Route path="/summary" element={<GameSummary />} />
</Routes>
</Router>
</Container>
);
}
export default AppRouter;

118
asc/src/ScoreContext.js Normal file
View File

@@ -0,0 +1,118 @@
// File: src/context/ScoreContext.js
import React, { createContext, useContext, useReducer, useEffect } from 'react';
// Action types
export const ACTIONS = {
START_GAME: 'start_game',
END_GAME: 'end_game',
SAVE_GAME: 'save_game',
ADD_END: 'add_end', // Changed from UPDATE_SCORE to ADD_END to match ScoreTracker
// Add other actions as needed
};
// Initial state
const initialState = {
currentGame: null,
gameHistory: [],
// Add other state properties as needed
};
// Get saved state from localStorage if available
const getSavedState = () => {
try {
const savedState = localStorage.getItem('archeryScores');
if (savedState) {
return JSON.parse(savedState);
}
} catch (error) {
console.error('Error loading saved game state:', error);
}
return initialState;
};
// Reducer function
const scoreReducer = (state, action) => {
switch (action.type) {
case ACTIONS.START_GAME:
const newGame = {
gameType: action.payload.gameType,
isLeague: action.payload.isLeague,
targetFace: action.payload.targetFace,
dateStarted: new Date().toISOString(),
ends: [], // Changed from scores to ends to match ScoreTracker expectations
// Add other game properties as needed
};
return {
...state,
currentGame: newGame,
};
case ACTIONS.END_GAME:
return {
...state,
currentGame: null,
};
case ACTIONS.SAVE_GAME:
// Only save if there's a current game
if (!state.currentGame) return state;
const completedGame = {
...state.currentGame,
dateCompleted: new Date().toISOString(),
};
return {
...state,
gameHistory: [...state.gameHistory, completedGame],
};
case ACTIONS.ADD_END: // Changed from UPDATE_SCORE to ADD_END
// Handle adding a new end
if (!state.currentGame) return state;
return {
...state,
currentGame: {
...state.currentGame,
ends: [...(state.currentGame.ends || []), action.payload], // Updated to use ends instead of scores
}
};
default:
return state;
}
};
// Create context
const ScoreContext = createContext();
// Provider component
export const ScoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(scoreReducer, getSavedState());
// Save state to localStorage whenever it changes
useEffect(() => {
try {
localStorage.setItem('archeryScores', JSON.stringify(state));
} catch (error) {
console.error('Error saving game state:', error);
}
}, [state]);
return (
<ScoreContext.Provider value={{ state, dispatch }}>
{children}
</ScoreContext.Provider>
);
};
// Custom hook for using the score context
export const useScore = () => {
const context = useContext(ScoreContext);
if (context === undefined) {
throw new Error('useScore must be used within a ScoreProvider');
}
return context;
};

View File

@@ -1,216 +1,215 @@
import React, { useState } from 'react'; // File: src/components/GameSetup.js
import { useScore, ACTIONS } from '../context/ScoreContext'; import React, { useState, useEffect } from 'react';
import { Button, Grid, Typography, TextField, Box } from '@mui/material';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import {
Button,
Card,
CardContent,
CardHeader,
Grid,
Typography,
FormControl,
FormControlLabel,
FormLabel,
Radio,
RadioGroup,
Divider
} from '@mui/material';
import { useScore, ACTIONS } from '../context/ScoreContext';
const ScoreTracker = () => { const GameSetup = () => {
const { state, dispatch } = useScore(); const { dispatch } = useScore();
const navigate = useNavigate(); const navigate = useNavigate();
const [selectedGameType, setSelectedGameType] = useState('');
const [selectedTargetFace, setSelectedTargetFace] = useState('standard');
const [isLeague, setIsLeague] = useState(false);
const gameType = state.currentGame.gameType; // For debugging - will show in browser console
useEffect(() => {
console.log("GameSetup.js: League mode state:", isLeague);
}, [isLeague]);
// Determine the number of arrows per round based on game type const handleGameTypeSelect = (gameType) => {
const maxArrowsPerRound = gameType === '450' ? 3 : 5; console.log("GameSetup.js: Setting game type to:", gameType);
const maxScore = gameType === '450' ? 10 : 5; setSelectedGameType(gameType);
const maxRounds = gameType === '450' ? 16 : 12; setSelectedTargetFace('standard'); // Reset to standard when game type changes
const [arrowScores, setArrowScores] = useState(Array(maxArrowsPerRound).fill(''));
const handleScoreChange = (index, value) => {
const updatedScores = [...arrowScores];
updatedScores[index] = value;
setArrowScores(updatedScores);
}; };
const handleAddRound = () => { const handleGameModeChange = (event) => {
const valid = arrowScores.slice(0, maxArrowsPerRound).every(score => // Direct conversion to boolean using === comparison
(score >= 0 && score <= maxScore) || score.toUpperCase() === 'X' const leagueMode = event.target.value === "league";
); console.log("GameSetup.js: Setting isLeague to:", leagueMode);
setIsLeague(leagueMode);
};
if (!valid) { const startGame = () => {
alert(`Please enter valid scores between 0-${maxScore} or X for bullseyes.`); console.log("GameSetup.js: Start Game button clicked");
console.log("GameSetup.js: Current state:", {
selectedGameType,
isLeague,
selectedTargetFace
});
if (!selectedGameType) {
console.log("GameSetup.js: No game type selected, returning early");
return; return;
} }
const roundArrows = arrowScores.slice(0, maxArrowsPerRound).map((score) => { try {
const arrowScore = score.toUpperCase() === 'X' ? maxScore : parseInt(score, 10); console.log("GameSetup.js: Dispatching START_GAME action");
return {
score: arrowScore,
isBullseye: score.toUpperCase() === 'X',
};
});
const roundTotal = roundArrows.reduce((sum, arrow) => sum + arrow.score, 0);
dispatch({ dispatch({
type: ACTIONS.ADD_ROUND, type: ACTIONS.START_GAME,
payload: { payload: {
roundIndex: state.currentGame.rounds.length, gameType: selectedGameType,
arrows: roundArrows, isLeague: isLeague,
total: roundTotal targetFace: selectedTargetFace
}, }
}); });
console.log("GameSetup.js: Dispatch completed successfully");
setArrowScores(Array(maxArrowsPerRound).fill('')); // Use React Router navigation
navigate('/score');
if (state.currentGame.rounds.length >= maxRounds - 1) { } catch (error) {
navigate('/summary'); console.error("GameSetup.js: Error in startGame function:", error);
} }
}; };
const handleMainMenu = () => { // Determine if we should show target face options
dispatch({ type: ACTIONS.RESET_GAME }); const showTargetFaceOptions = selectedGameType !== '';
navigate('/');
};
return ( return (
<Grid container spacing={2} justifyContent="center"> <Grid container spacing={3} justifyContent="center" alignItems="center" style={{ minHeight: '80vh' }}>
{/* Main Menu Button */} <Grid item xs={12} sm={8} md={6}>
<Box <Card>
sx={{ <CardHeader
position: 'absolute', title="Start New Game"
top: 16, titleTypographyProps={{ align: 'center' }}
left: 16, />
}} <CardContent>
> <Grid container spacing={3}>
<Button
variant="outlined"
onClick={handleMainMenu}
>
Main Menu
</Button>
</Box>
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="h5" align="center" gutterBottom> <Typography variant="body1" align="center" gutterBottom>
{gameType} Round - Round {state.currentGame.rounds.length + 1} Select your game type:
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}>
{/* Score input section */}
<Grid item xs={12}>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
gap: 1,
mb: 2
}}
>
{Array.from({ length: maxArrowsPerRound }).map((_, index) => (
<TextField
key={index}
value={arrowScores[index]}
onChange={(e) => handleScoreChange(index, e.target.value)}
placeholder="0"
size="small"
sx={{
width: '60px',
'& .MuiInputBase-input': {
padding: '8px',
textAlign: 'center'
}
}}
inputProps={{
maxLength: 1,
style: { textAlign: 'center' }
}}
/>
))}
</Box>
</Grid>
{/* Score buttons */}
<Grid item xs={12}>
<Box
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
gap: 1,
mb: 2
}}
>
{Array.from({ length: maxScore + 1 }).map((_, i) => (
<Button
key={i}
variant="outlined"
size="small"
sx={{ minWidth: '40px', height: '40px' }}
onClick={() => {
const emptyIndex = arrowScores.findIndex(score => score === '');
if (emptyIndex >= 0 && emptyIndex < maxArrowsPerRound) {
handleScoreChange(emptyIndex, i.toString());
}
}}
>
{i}
</Button>
))}
<Button
variant="outlined"
size="small"
sx={{ minWidth: '40px', height: '40px' }}
onClick={() => {
const emptyIndex = arrowScores.findIndex(score => score === '');
if (emptyIndex >= 0 && emptyIndex < maxArrowsPerRound) {
handleScoreChange(emptyIndex, 'X');
}
}}
>
X
</Button>
</Box>
</Grid>
{/* Control buttons */}
<Grid item xs={12}>
<Box sx={{ display: 'flex', justifyContent: 'center', gap: 2 }}>
<Button <Button
fullWidth
variant="contained" variant="contained"
color="primary" color="primary"
onClick={handleAddRound} size={selectedGameType === '450' ? "large" : "medium"}
disabled={!arrowScores.slice(0, maxArrowsPerRound).every(score => score !== '')} onClick={() => handleGameTypeSelect('450')}
style={{
backgroundColor: selectedGameType === '450' ? undefined : '#1976d2',
boxShadow: selectedGameType === '450' ? '0 0 10px #1976d2' : undefined
}}
> >
Add Round 450 Round
</Button> </Button>
</Grid>
<Grid item xs={6}>
<Button <Button
variant="outlined" fullWidth
onClick={() => setArrowScores(Array(maxArrowsPerRound).fill(''))} variant="contained"
color="secondary"
size={selectedGameType === '300' ? "large" : "medium"}
onClick={() => handleGameTypeSelect('300')}
style={{
backgroundColor: selectedGameType === '300' ? undefined : '#9c27b0',
boxShadow: selectedGameType === '300' ? '0 0 10px #9c27b0' : undefined
}}
> >
Clear 300 Round
</Button> </Button>
</Box>
</Grid> </Grid>
{/* Scores display */} {showTargetFaceOptions && (
<>
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="h6" align="center"> <Divider style={{ margin: '16px 0' }} />
Total Score: {state.currentGame.totalScore} | <Typography variant="body1" align="center" gutterBottom>
Bullseyes: {state.currentGame.totalBullseyes} Game Settings:
</Typography> </Typography>
</Grid> </Grid>
{/* Round history */} {/* Game Mode Selection */}
<Grid item xs={12}> <Grid item xs={12}>
<Box sx={{ maxHeight: '200px', overflow: 'auto' }}> <FormControl component="fieldset" style={{ marginBottom: '16px', width: '100%' }}>
{state.currentGame.rounds.length > 0 ? ( <FormLabel component="legend">Game Mode</FormLabel>
state.currentGame.rounds.map((round, roundIndex) => ( <RadioGroup
<Typography key={roundIndex} variant="body2" align="center"> row
Round {roundIndex + 1}: {round.arrows.map(arrow => arrow.score).join(', ')} name="gameMode"
(Total: {round.total}, Bullseyes: {round.bullseyes}) value={isLeague ? "league" : "practice"}
</Typography> onChange={handleGameModeChange}
)) >
) : ( <FormControlLabel
<Typography variant="body2" align="center"> value="practice"
No rounds played yet. control={<Radio />}
</Typography> label="Practice"
/>
<FormControlLabel
value="league"
control={<Radio />}
label="League"
/>
</RadioGroup>
</FormControl>
</Grid>
{/* Target Face Selection */}
<Grid item xs={12}>
<FormControl component="fieldset" style={{ width: '100%' }}>
<FormLabel component="legend">Target Face</FormLabel>
<RadioGroup
row
name="targetFace"
value={selectedTargetFace}
onChange={(e) => setSelectedTargetFace(e.target.value)}
>
<FormControlLabel
value="standard"
control={<Radio />}
label="Standard"
/>
{selectedGameType === '300' && (
<FormControlLabel
value="5-spot"
control={<Radio />}
label="5-Spot"
/>
)} )}
</Box> {selectedGameType === '450' && (
<FormControlLabel
value="3-spot"
control={<Radio />}
label="3-Spot"
/>
)}
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12} style={{ marginTop: '16px' }}>
<Button
fullWidth
variant="contained"
color="success"
size="large"
onClick={startGame}
id="start-game-button"
>
Start Game
</Button>
</Grid>
</>
)}
</Grid>
</CardContent>
</Card>
</Grid> </Grid>
</Grid> </Grid>
); );
}; };
export default ScoreTracker; export default GameSetup;

View File

@@ -0,0 +1,219 @@
// filename: GameSetup.js
import React, { useState, useEffect } from 'react';
import {
Button,
Card,
CardContent,
CardHeader,
Grid,
Typography,
FormControl,
FormControlLabel,
FormLabel,
Radio,
RadioGroup,
Divider
} from '@mui/material';
import { useScore, ACTIONS } from '../context/ScoreContext';
const GameSetup = ({ onGameStart }) => {
const { dispatch } = useScore();
const [selectedGameType, setSelectedGameType] = useState('');
const [selectedTargetFace, setSelectedTargetFace] = useState('standard');
const [isLeague, setIsLeague] = useState(false);
// For debugging - will show in browser console
useEffect(() => {
console.log("GameSetup.js: League mode state:", isLeague);
}, [isLeague]);
const handleGameTypeSelect = (gameType) => {
console.log("GameSetup.js: Setting game type to:", gameType);
setSelectedGameType(gameType);
setSelectedTargetFace('standard'); // Reset to standard when game type changes
};
const handleGameModeChange = (event) => {
// Direct conversion to boolean using === comparison
const leagueMode = event.target.value === "league";
console.log("GameSetup.js: Setting isLeague to:", leagueMode);
setIsLeague(leagueMode);
};
const startGame = () => {
console.log("GameSetup.js: Start Game button clicked");
console.log("GameSetup.js: Current state:", {
selectedGameType,
isLeague,
selectedTargetFace
});
if (!selectedGameType) {
console.log("GameSetup.js: No game type selected, returning early");
return;
}
try {
console.log("GameSetup.js: Dispatching START_GAME action");
dispatch({
type: ACTIONS.START_GAME,
payload: {
gameType: selectedGameType,
isLeague: isLeague,
targetFace: selectedTargetFace
}
});
console.log("GameSetup.js: Dispatch completed successfully");
if (typeof onGameStart === 'function') {
console.log("GameSetup.js: Calling onGameStart function");
onGameStart();
} else {
console.log("GameSetup.js: onGameStart is not a function or is undefined:", onGameStart);
}
} catch (error) {
console.error("GameSetup.js: Error in startGame function:", error);
}
};
// Determine if we should show target face options
const showTargetFaceOptions = selectedGameType !== '';
return (
<Grid container spacing={3} justifyContent="center" alignItems="center" style={{ minHeight: '80vh' }}>
<Grid item xs={12} sm={8} md={6}>
<Card>
<CardHeader
title="Start New Game"
titleTypographyProps={{ align: 'center' }}
/>
<CardContent>
<Grid container spacing={3}>
<Grid item xs={12}>
<Typography variant="body1" align="center" gutterBottom>
Select your game type:
</Typography>
</Grid>
<Grid item xs={6}>
<Button
fullWidth
variant="contained"
color="primary"
size={selectedGameType === '450' ? "large" : "medium"}
onClick={() => handleGameTypeSelect('450')}
style={{
backgroundColor: selectedGameType === '450' ? undefined : '#1976d2',
boxShadow: selectedGameType === '450' ? '0 0 10px #1976d2' : undefined
}}
>
450 Round
</Button>
</Grid>
<Grid item xs={6}>
<Button
fullWidth
variant="contained"
color="secondary"
size={selectedGameType === '300' ? "large" : "medium"}
onClick={() => handleGameTypeSelect('300')}
style={{
backgroundColor: selectedGameType === '300' ? undefined : '#9c27b0',
boxShadow: selectedGameType === '300' ? '0 0 10px #9c27b0' : undefined
}}
>
300 Round
</Button>
</Grid>
{showTargetFaceOptions && (
<>
<Grid item xs={12}>
<Divider style={{ margin: '16px 0' }} />
<Typography variant="body1" align="center" gutterBottom>
Game Settings:
</Typography>
</Grid>
{/* Game Mode Selection */}
<Grid item xs={12}>
<FormControl component="fieldset" style={{ marginBottom: '16px', width: '100%' }}>
<FormLabel component="legend">Game Mode</FormLabel>
<RadioGroup
row
name="gameMode"
value={isLeague ? "league" : "practice"}
onChange={handleGameModeChange}
>
<FormControlLabel
value="practice"
control={<Radio />}
label="Practice"
/>
<FormControlLabel
value="league"
control={<Radio />}
label="League"
/>
</RadioGroup>
</FormControl>
</Grid>
{/* Target Face Selection */}
<Grid item xs={12}>
<FormControl component="fieldset" style={{ width: '100%' }}>
<FormLabel component="legend">Target Face</FormLabel>
<RadioGroup
row
name="targetFace"
value={selectedTargetFace}
onChange={(e) => setSelectedTargetFace(e.target.value)}
>
<FormControlLabel
value="standard"
control={<Radio />}
label="Standard"
/>
{selectedGameType === '300' && (
<FormControlLabel
value="5-spot"
control={<Radio />}
label="5-Spot"
/>
)}
{selectedGameType === '450' && (
<FormControlLabel
value="3-spot"
control={<Radio />}
label="3-Spot"
/>
)}
</RadioGroup>
</FormControl>
</Grid>
<Grid item xs={12} style={{ marginTop: '16px' }}>
<Button
fullWidth
variant="contained"
color="success"
size="large"
onClick={startGame}
id="start-game-button" // Adding an ID for easier debugging
>
Start Game
</Button>
</Grid>
</>
)}
</Grid>
</CardContent>
</Card>
</Grid>
</Grid>
);
};
export default GameSetup;

View File

@@ -1,156 +1,319 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { import {
Button, Button,
Card, Card,
CardContent, CardContent,
CardHeader, CardHeader,
FormControl,
FormControlLabel,
RadioGroup,
Radio,
Grid, Grid,
Typography, Typography,
Select, Box,
MenuItem Paper,
IconButton,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Divider,
ButtonGroup
} from '@mui/material'; } from '@mui/material';
import { Remove } from '@mui/icons-material';
import { useScore, ACTIONS } from '../context/ScoreContext'; import { useScore, ACTIONS } from '../context/ScoreContext';
const GameSetup = ({ onGameStart }) => { const ScoreTracker = () => {
const { dispatch } = useScore(); const { state, dispatch } = useScore();
const navigate = useNavigate();
const [selectedGameType, setSelectedGameType] = useState(''); // Local state for the current end
const [isLeague, setIsLeague] = useState(false); // This tracks if it's a league game const [currentEnd, setCurrentEnd] = useState([]);
const [selectedTargetFace, setSelectedTargetFace] = useState('single'); // Assuming single as default target face
// Log isLeague to verify if it changes correctly // Check if we have an active game, if not redirect to setup
useEffect(() => { useEffect(() => {
console.log("Game mode isLeague:", isLeague); // Check this in the console if (!state.currentGame || !state.currentGame.gameType) {
}, [isLeague]); navigate('/');
const handleGameModeChange = (event) => {
const leagueMode = event.target.value === "league";
setIsLeague(leagueMode); // Update isLeague based on user selection
};
const handleTargetFaceChange = (event) => {
setSelectedTargetFace(event.target.value);
};
const startGame = () => {
if (!selectedGameType) return;
// Log the values to ensure they're correct
console.log("Starting game with settings:", {
gameType: selectedGameType,
isLeague: isLeague,
targetFace: selectedTargetFace
});
dispatch({
type: ACTIONS.START_GAME,
payload: {
gameType: selectedGameType,
isLeague: isLeague, // Pass the league/practice mode correctly
targetFace: selectedTargetFace
} }
}); }, [state.currentGame, navigate]);
onGameStart();
// Handle adding an arrow score
const handleAddArrow = (score) => {
// Add to current end
setCurrentEnd([...currentEnd, score]);
}; };
// Handle removing the last arrow
const handleRemoveArrow = () => {
if (currentEnd.length > 0) {
const newEnd = [...currentEnd];
newEnd.pop();
setCurrentEnd(newEnd);
}
};
// Calculate total for current end
const calculateEndTotal = (arrows) => {
return arrows.reduce((total, arrow) => {
if (arrow.toUpperCase() === 'X') {
return total + 10;
} else if (arrow.toUpperCase() === 'M') {
return total;
} else {
return total + parseInt(arrow);
}
}, 0);
};
// Get button color based on score
const getButtonColor = (score) => {
switch(score) {
case 'X':
return { bgcolor: '#FF9800', color: 'white' }; // Orange
case '10':
return { bgcolor: '#9C27B0', color: 'white' }; // Purple
case '9':
case '8':
return { bgcolor: '#2196F3', color: 'white' }; // Blue
case '7':
case '6':
case '5':
case '4':
case '3':
case '2':
case '1':
return { bgcolor: '#4CAF50', color: 'white' }; // Green
case 'M':
return { bgcolor: '#9E9E9E', color: 'white' }; // Gray
default:
return { bgcolor: 'default' };
}
};
// Handle saving the current end
const handleSaveEnd = () => {
if (currentEnd.length > 0) {
// Create the end object
const endObject = {
arrows: [...currentEnd],
total: calculateEndTotal(currentEnd),
timestamp: new Date().toISOString()
};
// Dispatch to context
dispatch({
type: ACTIONS.ADD_END,
payload: endObject
});
// Clear current end
setCurrentEnd([]);
}
};
// Handle ending the game
const handleEndGame = () => {
// Save final scores to history first
dispatch({
type: ACTIONS.SAVE_GAME,
});
// Then end the current game
dispatch({
type: ACTIONS.END_GAME,
});
// Navigate to setup page
navigate('/');
};
// Early return if no game is active
if (!state.currentGame || !state.currentGame.gameType) {
return null; // Will be redirected by useEffect
}
// Get the number of arrows per end based on game type
const arrowsPerEnd = state.currentGame.gameType === '450' ? 6 : 5;
// Score button values
const scoreButtons = ['X', '10', '9', '8', '7', '6', '5', '4', '3', '2', '1', 'M'];
return ( return (
<Grid container spacing={3} justifyContent="center" alignItems="center" style={{ minHeight: '80vh' }}> <Grid container spacing={3} justifyContent="center">
<Grid item xs={12} sm={8} md={6}> <Grid item xs={12} sm={10} md={8}>
<Card> <Card>
<CardHeader <CardHeader
title="Start New Game" title={`${state.currentGame.gameType} Round ${state.currentGame.isLeague ? '(League)' : '(Practice)'}`}
titleTypographyProps={{ align: 'center' }} titleTypographyProps={{ align: 'center' }}
/> />
<CardContent> <CardContent>
<Grid container spacing={3}> <Typography variant="h6" gutterBottom>
<Grid item xs={12}> Target Face: {state.currentGame.targetFace || 'Standard'}
<Typography variant="body1" align="center" gutterBottom>
Select your game type:
</Typography> </Typography>
</Grid>
<Grid item xs={6}> {/* Current game stats */}
<Box sx={{ mb: 3 }}>
<Paper elevation={2} sx={{ p: 2 }}>
<Typography variant="h6" gutterBottom>
Current Score: {state.currentGame.ends?.reduce((total, end) => total + end.total, 0) || 0}
</Typography>
<Typography variant="body1">
Ends Completed: {state.currentGame.ends?.length || 0}
</Typography>
</Paper>
</Box>
{/* Score input section */}
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom>
Score Current End
</Typography>
{/* Score buttons */}
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', flexWrap: 'wrap', mb: 1, gap: 0.5 }}>
{scoreButtons.map((score) => {
const colorStyle = getButtonColor(score);
return (
<Button <Button
fullWidth key={score}
variant="contained" variant="contained"
color={selectedGameType === '450' ? 'primary' : 'default'} onClick={() => handleAddArrow(score)}
size="large" disabled={currentEnd.length >= arrowsPerEnd}
onClick={() => setSelectedGameType('450')} sx={{
minWidth: '45px',
bgcolor: colorStyle.bgcolor,
color: colorStyle.color,
'&:hover': {
bgcolor: colorStyle.bgcolor,
opacity: 0.9
}
}}
> >
450 Round {score}
</Button> </Button>
</Grid> );
<Grid item xs={6}> })}
<Button
fullWidth
variant="contained"
color={selectedGameType === '300' ? 'secondary' : 'default'}
size="large"
onClick={() => setSelectedGameType('300')}
>
300 Round
</Button>
</Grid>
{/* Select League or Practice Mode */} <IconButton
<Grid item xs={12}> color="secondary"
<FormControl component="fieldset" fullWidth> onClick={handleRemoveArrow}
<Typography variant="body1" align="center" gutterBottom> disabled={currentEnd.length === 0}
Select game mode: sx={{ ml: 1 }}
>
<Remove />
</IconButton>
</Box>
</Box>
{/* Current end display */}
<Box sx={{ display: 'flex', flexWrap: 'wrap', mb: 2 }}>
{currentEnd.map((arrow, index) => {
let bgColor;
if (arrow.toUpperCase() === 'X') bgColor = '#FF9800'; // Orange
else if (arrow.toUpperCase() === '10') bgColor = '#9C27B0'; // Purple
else if (['9', '8'].includes(arrow)) bgColor = '#2196F3'; // Blue
else if (['7', '6', '5', '4', '3', '2', '1'].includes(arrow)) bgColor = '#4CAF50'; // Green
else if (arrow.toUpperCase() === 'M') bgColor = '#9E9E9E'; // Gray
else bgColor = 'default';
return (
<Paper
key={index}
elevation={1}
sx={{
p: 1.5,
m: 0.5,
minWidth: '40px',
textAlign: 'center',
bgcolor: bgColor,
color: 'white'
}}
>
<Typography variant="h6">{arrow.toUpperCase()}</Typography>
</Paper>
);
})}
{Array(arrowsPerEnd - currentEnd.length).fill(0).map((_, index) => (
<Paper
key={`empty-${index}`}
elevation={1}
sx={{
p: 1.5,
m: 0.5,
minWidth: '40px',
textAlign: 'center',
bgcolor: '#f5f5f5'
}}
>
<Typography variant="h6">-</Typography>
</Paper>
))}
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body1">
End Total: {calculateEndTotal(currentEnd)}
</Typography> </Typography>
<RadioGroup
row
name="gameMode"
value={isLeague ? "league" : "practice"} // Display correct mode
onChange={handleGameModeChange}
>
<FormControlLabel
value="practice"
control={<Radio />}
label="Practice"
/>
<FormControlLabel
value="league"
control={<Radio />}
label="League"
/>
</RadioGroup>
</FormControl>
</Grid>
{/* Target face selection */}
<Grid item xs={12}>
<FormControl fullWidth>
<Typography variant="body1" align="center" gutterBottom>
Select target face:
</Typography>
<Select
value={selectedTargetFace}
onChange={handleTargetFaceChange}
>
<MenuItem value="single">Single Spot</MenuItem>
<MenuItem value="five">Five Spot</MenuItem>
</Select>
</FormControl>
</Grid>
{/* Start Game Button */}
<Grid item xs={12}>
<Button <Button
fullWidth
variant="contained" variant="contained"
color="primary" color="primary"
size="large" onClick={handleSaveEnd}
onClick={startGame} disabled={currentEnd.length === 0 || currentEnd.length < arrowsPerEnd}
disabled={!selectedGameType} // Disable button if no game type selected
> >
Start Game Save End
</Button>
</Box>
</Box>
<Divider sx={{ my: 3 }} />
{/* Previous ends display */}
{(state.currentGame.ends?.length > 0) && (
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom>
Previous Ends
</Typography>
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>End</TableCell>
<TableCell>Arrows</TableCell>
<TableCell>Total</TableCell>
<TableCell>Running Total</TableCell>
</TableRow>
</TableHead>
<TableBody>
{state.currentGame.ends.map((end, endIndex) => {
const runningTotal = state.currentGame.ends
.slice(0, endIndex + 1)
.reduce((total, e) => total + e.total, 0);
return (
<TableRow key={endIndex}>
<TableCell>{endIndex + 1}</TableCell>
<TableCell>
{end.arrows.join(', ')}
</TableCell>
<TableCell>{end.total}</TableCell>
<TableCell>{runningTotal}</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
</Box>
)}
<Button
variant="contained"
color="secondary"
onClick={handleEndGame}
sx={{ mt: 3 }}
>
End Game
</Button> </Button>
</Grid>
</Grid>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
@@ -158,5 +321,4 @@ const GameSetup = ({ onGameStart }) => {
); );
}; };
export default GameSetup; export default ScoreTracker;