diff --git a/asc/src/App.js b/asc/src/App.js
index 06f09b4..c0aab3b 100644
--- a/asc/src/App.js
+++ b/asc/src/App.js
@@ -1,4 +1,4 @@
-import React, { useState, useMemo, useEffect } from 'react';
+import React, { useState, useMemo } from 'react';
import { CssBaseline, Container, IconButton } from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { Brightness7, Brightness4 } from '@mui/icons-material';
diff --git a/asc/src/AppRouter.js b/asc/src/AppRouter.js
index f04075e..0d507b9 100644
--- a/asc/src/AppRouter.js
+++ b/asc/src/AppRouter.js
@@ -6,6 +6,8 @@ import { Brightness7, Brightness4 } from '@mui/icons-material';
import GameSetup from './components/GameSetup';
import ScoreTracker from './components/ScoreTracker';
import GameSummary from './components/GameSummary';
+import History from './components/History'; // Import the History component
+import MainMenu from './components/MainMenu'; // Import MainMenu
import { useTheme } from './context/ThemeContext';
import { useScore } from './context/ScoreContext';
@@ -39,19 +41,25 @@ function AppRouter() {
- {/* Home route - redirects to score if a game is active */}
+ {/* Home route - displays MainMenu, or redirects to score if a game is active */}
:
-
+ :
+
}
/>
- {/* Explicit score tracker route */}
+ {/* Game setup route - keeping it separate but we can remove if not needed */}
}
+ />
+
+ {/* Score tracker route */}
+ :
@@ -65,6 +73,12 @@ function AppRouter() {
element={}
/>
+ {/* History route */}
+ }
+ />
+
{/* Catch-all route for any invalid URLs */}
} />
diff --git a/asc/src/components/History.js b/asc/src/components/History.js
index c149e6b..6aa9491 100644
--- a/asc/src/components/History.js
+++ b/asc/src/components/History.js
@@ -1,7 +1,8 @@
import React from 'react';
import { useScore } from '../context/ScoreContext';
import { useNavigate } from 'react-router-dom';
-import { Box, Typography, Button, Card, CardContent, Grid } from '@mui/material';
+import { Box, Typography, Button, Grid, Chip, Accordion, AccordionSummary, AccordionDetails, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from '@mui/material';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
const History = () => {
const { state } = useScore();
@@ -9,7 +10,7 @@ const History = () => {
// Group games by date for better organization
const groupedGames = state.history.reduce((groups, game) => {
- const date = new Date(game.date).toLocaleDateString();
+ const date = new Date(game.dateStarted || game.date).toLocaleDateString();
if (!groups[date]) {
groups[date] = [];
}
@@ -17,6 +18,37 @@ const History = () => {
return groups;
}, {});
+ // Function to get color for score chip
+ const getScoreColor = (score, gameType) => {
+ if (gameType === '300') {
+ if (score >= 290) return 'success';
+ if (score >= 280) return 'primary';
+ if (score >= 270) return 'secondary';
+ return 'default';
+ } else { // 450 game
+ if (score >= 440) return 'success';
+ if (score >= 430) return 'primary';
+ if (score >= 410) return 'secondary';
+ return 'default';
+ }
+ };
+
+ // Calculate total statistics from all ends
+ const calculateEndStats = (ends) => {
+ if (!ends || ends.length === 0) return { totalScore: 0, bullseyes: 0, average: 0 };
+
+ const totalScore = ends.reduce((sum, end) => sum + end.total, 0);
+ const bullseyes = ends.reduce((sum, end) => {
+ return sum + end.arrows.filter(a => a.toUpperCase() === 'X').length;
+ }, 0);
+
+ return {
+ totalScore,
+ bullseyes,
+ average: (totalScore / ends.length).toFixed(1)
+ };
+ };
+
return (
{
{date}
- {games.map((game, index) => (
-
-
-
-
- {game.gameType} Round
-
-
- Total Score: {game.totalScore}
-
-
- Bullseyes: {game.totalBullseyes}
-
-
- Average: {(game.totalScore / game.rounds.length).toFixed(1)}
-
-
-
-
- ))}
+ {games.map((game, index) => {
+ // Get statistics from ends if available
+ const stats = game.ends && game.ends.length > 0
+ ? calculateEndStats(game.ends)
+ : {
+ totalScore: game.totalScore || 0,
+ bullseyes: game.totalBullseyes || 0,
+ average: game.rounds && game.rounds.length > 0
+ ? (game.totalScore / game.rounds.length).toFixed(1)
+ : 0
+ };
+
+ return (
+
+
+ }
+ aria-controls={`panel${index}-content`}
+ id={`panel${index}-header`}
+ >
+
+
+
+ {game.gameType} League Round
+
+
+
+
+ {game.targetFace === 'standard' ? 'Standard Target' : `${game.targetFace} Target`}
+
+
+
+
+
+
+ Bullseyes: {stats.bullseyes}
+
+
+ Average per End: {stats.average}
+
+
+
+ {game.ends && game.ends.length > 0 && (
+
+
+
+
+ End
+ Arrows
+ Total
+ Running
+
+
+
+ {game.ends.map((end, endIndex) => {
+ // Calculate running total
+ const runningTotal = game.ends
+ .slice(0, endIndex + 1)
+ .reduce((sum, e) => sum + e.total, 0);
+
+ return (
+
+ {endIndex + 1}
+ {end.arrows.join(', ')}
+ {end.total}
+ {runningTotal}
+
+ );
+ })}
+
+
+
+ )}
+
+
+
+ );
+ })}
))
diff --git a/asc/src/components/ScoreTracker.js b/asc/src/components/ScoreTracker.js
index cc5bede..2be6a1a 100644
--- a/asc/src/components/ScoreTracker.js
+++ b/asc/src/components/ScoreTracker.js
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Button,
@@ -33,7 +33,7 @@ const ScoreTracker = () => {
// Local state to track total arrows
const [totalArrows, setTotalArrows] = useState(0);
-// Navigate to History page after last round
+ // Navigate to History page after last round
useEffect(() => {
if (state.currentGame && state.currentGame.ends) {
const maxRounds = state.currentGame.gameType === '450' ? 16 : 12;
@@ -43,7 +43,6 @@ const ScoreTracker = () => {
}
}, [state.currentGame, navigate]);
-
// Check if we have an active game, if not redirect to setup
useEffect(() => {
if (!state.currentGame || !state.currentGame.gameType) {
@@ -51,6 +50,37 @@ const ScoreTracker = () => {
}
}, [state.currentGame, navigate]);
+ // Calculate score distribution for all completed ends
+ const calculateScoreDistribution = useCallback(() => {
+ // Initialize distribution object based on game type
+ let distribution = {};
+
+ if (state.currentGame.gameType === '300' && state.currentGame.targetFace === '5-spot') {
+ distribution = { 'X': 0, '5': 0, '4': 0, 'M': 0 };
+ } else if (state.currentGame.gameType === '300' && state.currentGame.targetFace !== '5-spot') {
+ distribution = { 'X': 0, '5': 0, '4': 0, '3': 0, '2': 0, '1': 0, 'M': 0 };
+ } else if (state.currentGame.gameType === '450' && state.currentGame.targetFace === '3-spot') {
+ distribution = { 'X': 0, '10': 0, '9': 0, '8': 0, 'M': 0 };
+ } else {
+ distribution = { 'X': 0, '10': 0, '9': 0, '8': 0, '7': 0, '6': 0, '5': 0, '4': 0, '3': 0, '2': 0, '1': 0, 'M': 0 };
+ }
+
+ // Count arrows from all completed ends
+ if (state.currentGame.ends && state.currentGame.ends.length > 0) {
+ state.currentGame.ends.forEach(end => {
+ end.arrows.forEach(arrow => {
+ // Make sure to handle uppercase X and M
+ const normalizedArrow = arrow.toUpperCase();
+ if (distribution.hasOwnProperty(normalizedArrow)) {
+ distribution[normalizedArrow]++;
+ }
+ });
+ });
+ }
+
+ return distribution;
+ }, [state.currentGame]);
+
// Update score distribution whenever the current game changes
useEffect(() => {
if (state.currentGame) {
@@ -58,7 +88,7 @@ const ScoreTracker = () => {
setScoreDistribution(distribution);
setTotalArrows(calculateTotalArrows(distribution));
}
- }, [state.currentGame]);
+ }, [state.currentGame, calculateScoreDistribution]);
// Handle adding an arrow score
const handleAddArrow = (score) => {
@@ -88,37 +118,6 @@ const ScoreTracker = () => {
}, 0);
};
- // Calculate score distribution for all completed ends
- const calculateScoreDistribution = () => {
- // Initialize distribution object based on game type
- let distribution = {};
-
- if (state.currentGame.gameType === '300' && state.currentGame.targetFace === '5-spot') {
- distribution = { 'X': 0, '5': 0, '4': 0, 'M': 0 };
- } else if (state.currentGame.gameType === '300' && state.currentGame.targetFace !== '5-spot') {
- distribution = { 'X': 0, '5': 0, '4': 0, '3': 0, '2': 0, '1': 0, 'M': 0 };
- } else if (state.currentGame.gameType === '450' && state.currentGame.targetFace === '3-spot') {
- distribution = { 'X': 0, '10': 0, '9': 0, '8': 0, 'M': 0 };
- } else {
- distribution = { 'X': 0, '10': 0, '9': 0, '8': 0, '7': 0, '6': 0, '5': 0, '4': 0, '3': 0, '2': 0, '1': 0, 'M': 0 };
- }
-
- // Count arrows from all completed ends
- if (state.currentGame.ends && state.currentGame.ends.length > 0) {
- state.currentGame.ends.forEach(end => {
- end.arrows.forEach(arrow => {
- // Make sure to handle uppercase X and M
- const normalizedArrow = arrow.toUpperCase();
- if (distribution.hasOwnProperty(normalizedArrow)) {
- distribution[normalizedArrow]++;
- }
- });
- });
- }
-
- return distribution;
- };
-
// Calculate total arrows shot
const calculateTotalArrows = (distribution) => {
return Object.values(distribution).reduce((sum, count) => sum + count, 0);
@@ -210,16 +209,8 @@ const ScoreTracker = () => {
payload: endObject
});
- // Update score distribution immediately
- // Create a new temporary game state that includes the new end
- const updatedEnds = [...(state.currentGame.ends || []), endObject];
- const tempGameState = {
- ...state.currentGame,
- ends: updatedEnds
- };
-
// Calculate distribution based on this updated state
- let updatedDistribution = { ...scoreDistribution };
+ const updatedDistribution = { ...scoreDistribution };
// Add the new arrows to the distribution
currentEnd.forEach(arrow => {
@@ -333,9 +324,11 @@ const ScoreTracker = () => {
{/* Only show Ends Completed for league games */}
{!isPracticeGame && (
-
-
- Ends Completed: {state.currentGame.ends?.length || 0}
+
+
+ Ends Completed: {state.currentGame.ends?.length || 0} of {state.currentGame.gameType === '450' ? 16:12 }
)}
@@ -579,15 +572,17 @@ const ScoreTracker = () => {
)}
-
+
+ {isPracticeGame && (
+ End Practice
+
+ )}
diff --git a/asc/src/context/ScoreContext.js b/asc/src/context/ScoreContext.js
index ea676dd..3ae5afc 100644
--- a/asc/src/context/ScoreContext.js
+++ b/asc/src/context/ScoreContext.js
@@ -3,10 +3,12 @@ import React, { createContext, useContext, useReducer, useEffect } from 'react';
export const ACTIONS = {
START_GAME: 'start_game',
ADD_ROUND: 'add_round',
+ ADD_END: 'add_end',
RESET_GAME: 'reset_game',
SAVE_GAME: 'save_game',
LOAD_HISTORY: 'load_history',
- SET_TARGET_FACE: 'set_target_face'
+ SET_TARGET_FACE: 'set_target_face',
+ END_GAME: 'end_game'
};
// Define the available score values for each game type and target face
@@ -27,6 +29,7 @@ const initialState = {
isLeague: false,
targetFace: 'standard', // Default to standard target face
rounds: [],
+ ends: [], // Added to store ends for the current game
totalScore: 0,
totalBullseyes: 0,
date: null,
@@ -43,7 +46,7 @@ const getAvailableScores = (gameType, targetFace) => {
const scoreReducer = (state, action) => {
switch (action.type) {
- case ACTIONS.START_GAME: // Changed from START_NEW_ROUND to match the exported ACTIONS
+ case ACTIONS.START_GAME:
const gameType = action.payload.gameType;
const targetFace = action.payload.targetFace || 'standard';
@@ -54,6 +57,7 @@ const scoreReducer = (state, action) => {
isLeague: action.payload.isLeague,
targetFace,
rounds: [],
+ ends: [], // Initialize empty ends array
totalScore: 0,
totalBullseyes: 0,
dateStarted: new Date().toISOString(),
@@ -94,12 +98,41 @@ const scoreReducer = (state, action) => {
},
};
+ case ACTIONS.ADD_END:
+ // Add a new end to the current game
+ const newEnds = [...(state.currentGame.ends || []), action.payload];
+
+ // Calculate new total score directly from ends
+ const newTotalScore = newEnds.reduce((sum, end) => sum + end.total, 0);
+
+ // Calculate new total bullseyes from all ends
+ const newTotalBullseyes = newEnds.reduce((sum, end) => {
+ return sum + end.arrows.filter(arrow => arrow.toUpperCase() === 'X').length;
+ }, 0);
+
+ return {
+ ...state,
+ currentGame: {
+ ...state.currentGame,
+ ends: newEnds,
+ totalScore: newTotalScore,
+ totalBullseyes: newTotalBullseyes
+ }
+ };
+
case ACTIONS.SAVE_GAME:
if (!state.currentGame.isLeague) return state; // Only save league games
+ // Create a complete game object with all necessary data
+ const gameToSave = {
+ ...state.currentGame,
+ date: new Date().toISOString(),
+ completed: true
+ };
+
const updatedHistory = [
...state.history,
- { ...state.currentGame }
+ gameToSave
];
// Save to localStorage
@@ -122,6 +155,13 @@ const scoreReducer = (state, action) => {
currentGame: initialState.currentGame
};
+ case ACTIONS.END_GAME:
+ // Reset the current game state to initial values
+ return {
+ ...state,
+ currentGame: initialState.currentGame
+ };
+
default:
return state;
}