From 19e63366926b5c5336f287a5cc9e98e306b62cb6 Mon Sep 17 00:00:00 2001 From: "corey@blaishome.online" Date: Tue, 18 Mar 2025 23:42:30 -0400 Subject: [PATCH] fixed navigation and updated buttons --- asc/src/App.js | 64 +++-- asc/src/AppRouter.js | 87 +++--- asc/src/AppRouter.old | 61 +++++ asc/src/ScoreContext.js | 118 ++++++++ asc/src/components/GameSetup.js | 389 ++++++++++++++------------- asc/src/components/GameSetup.old | 219 +++++++++++++++ asc/src/components/ScoreTracker.js | 416 ++++++++++++++++++++--------- 7 files changed, 963 insertions(+), 391 deletions(-) create mode 100644 asc/src/AppRouter.old create mode 100644 asc/src/ScoreContext.js create mode 100644 asc/src/components/GameSetup.old diff --git a/asc/src/App.js b/asc/src/App.js index 7f60fac..7c65503 100644 --- a/asc/src/App.js +++ b/asc/src/App.js @@ -1,16 +1,14 @@ import React, { useState, useMemo, useEffect } from 'react'; import { CssBaseline, Container, IconButton } from '@mui/material'; -import { ThemeProvider, createTheme } from '@mui/material/styles'; // <-- Import createTheme -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; -import { ScoreProvider } from './context/ScoreContext'; // <-- Import ScoreProvider - -import MainMenu from './components/MainMenu'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { Brightness7, Brightness4 } from '@mui/icons-material'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { ScoreProvider } from './context/ScoreContext'; import GameSetup from './components/GameSetup'; 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(() => { try { const savedMode = localStorage.getItem('themeMode'); @@ -42,36 +40,36 @@ function App() { return ( - - - + + - {mode === 'dark' ? : } - - + {/* Theme Toggle Button */} + + {mode === 'dark' ? : } + + + {/* Routes for navigation */} - } /> {/* MainMenu as the default page */} - } /> - } /> - } /> + } /> + } /> - - + + ); -} +}; export default App; - diff --git a/asc/src/AppRouter.js b/asc/src/AppRouter.js index a0cd59a..f04075e 100644 --- a/asc/src/AppRouter.js +++ b/asc/src/AppRouter.js @@ -1,60 +1,75 @@ // File: src/AppRouter.js -import React, { useState } from 'react'; -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import React from 'react'; +import { BrowserRouter as Router, Route, Routes, Navigate } 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'; +import { useScore } from './context/ScoreContext'; +// The main router component function AppRouter() { const { mode, toggleTheme } = useTheme(); + const { state } = useScore(); - 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; - }); + // Check if a game is currently in progress + const isGameActive = () => { + return state.currentGame && state.currentGame.gameType !== null; + }; return ( - - + - {mode === 'dark' ? : } - - + + {mode === 'dark' ? : } + + + {/* Home route - redirects to score if a game is active */} setGameStarted(true)} /> - : + isGameActive() ? + : + } /> - } /> + + {/* Explicit score tracker route */} + : + + } + /> + + {/* Summary route */} + } + /> + + {/* Catch-all route for any invalid URLs */} + } /> - - + + ); } diff --git a/asc/src/AppRouter.old b/asc/src/AppRouter.old new file mode 100644 index 0000000..a0cd59a --- /dev/null +++ b/asc/src/AppRouter.old @@ -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 ( + + + {mode === 'dark' ? : } + + + + setGameStarted(true)} /> + : + } + /> + } /> + + + + ); +} + +export default AppRouter; diff --git a/asc/src/ScoreContext.js b/asc/src/ScoreContext.js new file mode 100644 index 0000000..2045e9f --- /dev/null +++ b/asc/src/ScoreContext.js @@ -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 ( + + {children} + + ); +}; + +// 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; +}; diff --git a/asc/src/components/GameSetup.js b/asc/src/components/GameSetup.js index 54cf74a..026d8f2 100644 --- a/asc/src/components/GameSetup.js +++ b/asc/src/components/GameSetup.js @@ -1,216 +1,215 @@ -import React, { useState } from 'react'; -import { useScore, ACTIONS } from '../context/ScoreContext'; -import { Button, Grid, Typography, TextField, Box } from '@mui/material'; +// File: src/components/GameSetup.js +import React, { useState, useEffect } from 'react'; 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 { state, dispatch } = useScore(); +const GameSetup = () => { + const { dispatch } = useScore(); const navigate = useNavigate(); - - const gameType = state.currentGame.gameType; + const [selectedGameType, setSelectedGameType] = useState(''); + const [selectedTargetFace, setSelectedTargetFace] = useState('standard'); + const [isLeague, setIsLeague] = useState(false); - // Determine the number of arrows per round based on game type - const maxArrowsPerRound = gameType === '450' ? 3 : 5; - const maxScore = gameType === '450' ? 10 : 5; - const maxRounds = gameType === '450' ? 16 : 12; - - const [arrowScores, setArrowScores] = useState(Array(maxArrowsPerRound).fill('')); - - const handleScoreChange = (index, value) => { - const updatedScores = [...arrowScores]; - updatedScores[index] = value; - setArrowScores(updatedScores); + // 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 handleAddRound = () => { - const valid = arrowScores.slice(0, maxArrowsPerRound).every(score => - (score >= 0 && score <= maxScore) || score.toUpperCase() === 'X' - ); + 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 (!valid) { - alert(`Please enter valid scores between 0-${maxScore} or X for bullseyes.`); + if (!selectedGameType) { + console.log("GameSetup.js: No game type selected, returning early"); return; } - - const roundArrows = arrowScores.slice(0, maxArrowsPerRound).map((score) => { - const arrowScore = score.toUpperCase() === 'X' ? maxScore : parseInt(score, 10); - return { - score: arrowScore, - isBullseye: score.toUpperCase() === 'X', - }; - }); - - const roundTotal = roundArrows.reduce((sum, arrow) => sum + arrow.score, 0); - - dispatch({ - type: ACTIONS.ADD_ROUND, - payload: { - roundIndex: state.currentGame.rounds.length, - arrows: roundArrows, - total: roundTotal - }, - }); - - setArrowScores(Array(maxArrowsPerRound).fill('')); - - if (state.currentGame.rounds.length >= maxRounds - 1) { - navigate('/summary'); + + 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"); + + // Use React Router navigation + navigate('/score'); + + } catch (error) { + console.error("GameSetup.js: Error in startGame function:", error); } }; - const handleMainMenu = () => { - dispatch({ type: ACTIONS.RESET_GAME }); - navigate('/'); - }; + // Determine if we should show target face options + const showTargetFaceOptions = selectedGameType !== ''; return ( - - {/* Main Menu Button */} - - - + + + + + + + + + Select your game type: + + + + + + + + - - - {gameType} Round - Round {state.currentGame.rounds.length + 1} - - + {showTargetFaceOptions && ( + <> + + + + Game Settings: + + + + {/* Game Mode Selection */} + + + Game Mode + + } + label="Practice" + /> + } + label="League" + /> + + + + + {/* Target Face Selection */} + + + Target Face + setSelectedTargetFace(e.target.value)} + > + } + label="Standard" + /> + {selectedGameType === '300' && ( + } + label="5-Spot" + /> + )} + {selectedGameType === '450' && ( + } + label="3-Spot" + /> + )} + + + - {/* Score input section */} - - - {Array.from({ length: maxArrowsPerRound }).map((_, index) => ( - handleScoreChange(index, e.target.value)} - placeholder="0" - size="small" - sx={{ - width: '60px', - '& .MuiInputBase-input': { - padding: '8px', - textAlign: 'center' - } - }} - inputProps={{ - maxLength: 1, - style: { textAlign: 'center' } - }} - /> - ))} - - - - {/* Score buttons */} - - - {Array.from({ length: maxScore + 1 }).map((_, i) => ( - - ))} - - - - - {/* Control buttons */} - - - - - - - - {/* Scores display */} - - - Total Score: {state.currentGame.totalScore} | - Bullseyes: {state.currentGame.totalBullseyes} - - - - {/* Round history */} - - - {state.currentGame.rounds.length > 0 ? ( - state.currentGame.rounds.map((round, roundIndex) => ( - - Round {roundIndex + 1}: {round.arrows.map(arrow => arrow.score).join(', ')} - (Total: {round.total}, Bullseyes: {round.bullseyes}) - - )) - ) : ( - - No rounds played yet. - - )} - + + + + + )} + + + ); }; -export default ScoreTracker; - +export default GameSetup; diff --git a/asc/src/components/GameSetup.old b/asc/src/components/GameSetup.old new file mode 100644 index 0000000..8873b27 --- /dev/null +++ b/asc/src/components/GameSetup.old @@ -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 ( + + + + + + + + + Select your game type: + + + + + + + + + + {showTargetFaceOptions && ( + <> + + + + Game Settings: + + + + {/* Game Mode Selection */} + + + Game Mode + + } + label="Practice" + /> + } + label="League" + /> + + + + + {/* Target Face Selection */} + + + Target Face + setSelectedTargetFace(e.target.value)} + > + } + label="Standard" + /> + {selectedGameType === '300' && ( + } + label="5-Spot" + /> + )} + {selectedGameType === '450' && ( + } + label="3-Spot" + /> + )} + + + + + + + + + )} + + + + + + ); +}; + +export default GameSetup; diff --git a/asc/src/components/ScoreTracker.js b/asc/src/components/ScoreTracker.js index 1be2b61..c153744 100644 --- a/asc/src/components/ScoreTracker.js +++ b/asc/src/components/ScoreTracker.js @@ -1,156 +1,319 @@ import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Button, Card, CardContent, CardHeader, - FormControl, - FormControlLabel, - RadioGroup, - Radio, Grid, Typography, - Select, - MenuItem + Box, + Paper, + IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Divider, + ButtonGroup } from '@mui/material'; +import { Remove } from '@mui/icons-material'; import { useScore, ACTIONS } from '../context/ScoreContext'; -const GameSetup = ({ onGameStart }) => { - const { dispatch } = useScore(); - - const [selectedGameType, setSelectedGameType] = useState(''); - const [isLeague, setIsLeague] = useState(false); // This tracks if it's a league game - const [selectedTargetFace, setSelectedTargetFace] = useState('single'); // Assuming single as default target face - - // Log isLeague to verify if it changes correctly +const ScoreTracker = () => { + const { state, dispatch } = useScore(); + const navigate = useNavigate(); + + // Local state for the current end + const [currentEnd, setCurrentEnd] = useState([]); + + // Check if we have an active game, if not redirect to setup useEffect(() => { - console.log("Game mode isLeague:", isLeague); // Check this in the console - }, [isLeague]); - - const handleGameModeChange = (event) => { - const leagueMode = event.target.value === "league"; - setIsLeague(leagueMode); // Update isLeague based on user selection + if (!state.currentGame || !state.currentGame.gameType) { + navigate('/'); + } + }, [state.currentGame, navigate]); + + // Handle adding an arrow score + const handleAddArrow = (score) => { + // Add to current end + setCurrentEnd([...currentEnd, score]); }; - - const handleTargetFaceChange = (event) => { - setSelectedTargetFace(event.target.value); + + // Handle removing the last arrow + const handleRemoveArrow = () => { + if (currentEnd.length > 0) { + const newEnd = [...currentEnd]; + newEnd.pop(); + setCurrentEnd(newEnd); + } }; - - 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 - }); - + + // 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.START_GAME, - payload: { - gameType: selectedGameType, - isLeague: isLeague, // Pass the league/practice mode correctly - targetFace: selectedTargetFace - } + type: ACTIONS.SAVE_GAME, }); - onGameStart(); + + // 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 ( - - + + - - - - - Select your game type: + + Target Face: {state.currentGame.targetFace || 'Standard'} + + + {/* Current game stats */} + + + + Current Score: {state.currentGame.ends?.reduce((total, end) => total + end.total, 0) || 0} - - - - - - - - - {/* Select League or Practice Mode */} - - - - Select game mode: - - + Ends Completed: {state.currentGame.ends?.length || 0} + + + + + {/* Score input section */} + + + Score Current End + + + {/* Score buttons */} + + + {scoreButtons.map((score) => { + const colorStyle = getButtonColor(score); + return ( + + ); + })} + + - } - label="Practice" - /> - } - label="League" - /> - - - - - {/* Target face selection */} - - - - Select target face: - - - - - - {/* Start Game Button */} - - - - + + + + + + {/* Previous ends display */} + {(state.currentGame.ends?.length > 0) && ( + + + Previous Ends + + + + + + End + Arrows + Total + Running Total + + + + {state.currentGame.ends.map((end, endIndex) => { + const runningTotal = state.currentGame.ends + .slice(0, endIndex + 1) + .reduce((total, e) => total + e.total, 0); + + return ( + + {endIndex + 1} + + {end.arrows.join(', ')} + + {end.total} + {runningTotal} + + ); + })} + +
+
+
+ )} + +
@@ -158,5 +321,4 @@ const GameSetup = ({ onGameStart }) => { ); }; -export default GameSetup; - +export default ScoreTracker;