fixes to League performance
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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() {
|
||||
</IconButton>
|
||||
|
||||
<Routes>
|
||||
{/* Home route - redirects to score if a game is active */}
|
||||
{/* Home route - displays MainMenu, or redirects to score if a game is active */}
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
isGameActive() ?
|
||||
<Navigate to="/score" replace /> :
|
||||
<GameSetup />
|
||||
<Navigate to="/score-tracker" replace /> :
|
||||
<MainMenu />
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Explicit score tracker route */}
|
||||
{/* Game setup route - keeping it separate but we can remove if not needed */}
|
||||
<Route
|
||||
path="/score"
|
||||
path="/setup"
|
||||
element={<GameSetup />}
|
||||
/>
|
||||
|
||||
{/* Score tracker route */}
|
||||
<Route
|
||||
path="/score-tracker"
|
||||
element={
|
||||
isGameActive() ?
|
||||
<ScoreTracker /> :
|
||||
@@ -65,6 +73,12 @@ function AppRouter() {
|
||||
element={<GameSummary />}
|
||||
/>
|
||||
|
||||
{/* History route */}
|
||||
<Route
|
||||
path="/history"
|
||||
element={<History />}
|
||||
/>
|
||||
|
||||
{/* Catch-all route for any invalid URLs */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
|
||||
@@ -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 (
|
||||
<Box sx={{ p: 4 }}>
|
||||
<Box sx={{
|
||||
@@ -41,26 +73,88 @@ const History = () => {
|
||||
<Box key={date} sx={{ mb: 4 }}>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>{date}</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{games.map((game, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{game.gameType} Round
|
||||
{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 (
|
||||
<Grid item xs={12} sm={6} key={index}>
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-controls={`panel${index}-content`}
|
||||
id={`panel${index}-header`}
|
||||
>
|
||||
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', mb: 1 }}>
|
||||
<Typography variant="h6">
|
||||
{game.gameType} League Round
|
||||
</Typography>
|
||||
<Typography>
|
||||
Total Score: {game.totalScore}
|
||||
<Chip
|
||||
label={`${stats.totalScore} pts`}
|
||||
color={getScoreColor(stats.totalScore, game.gameType)}
|
||||
sx={{ fontWeight: 'bold' }}
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{game.targetFace === 'standard' ? 'Standard Target' : `${game.targetFace} Target`}
|
||||
</Typography>
|
||||
<Typography>
|
||||
Bullseyes: {game.totalBullseyes}
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body1" sx={{ mb: 1 }}>
|
||||
<strong>Bullseyes:</strong> {stats.bullseyes}
|
||||
</Typography>
|
||||
<Typography>
|
||||
Average: {(game.totalScore / game.rounds.length).toFixed(1)}
|
||||
<Typography variant="body1" sx={{ mb: 1 }}>
|
||||
<strong>Average per End:</strong> {stats.average}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
{game.ends && game.ends.length > 0 && (
|
||||
<TableContainer component={Paper} variant="outlined" sx={{ mb: 2 }}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>End</TableCell>
|
||||
<TableCell>Arrows</TableCell>
|
||||
<TableCell align="right">Total</TableCell>
|
||||
<TableCell align="right">Running</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{game.ends.map((end, endIndex) => {
|
||||
// Calculate running total
|
||||
const runningTotal = game.ends
|
||||
.slice(0, endIndex + 1)
|
||||
.reduce((sum, e) => sum + e.total, 0);
|
||||
|
||||
return (
|
||||
<TableRow key={endIndex}>
|
||||
<TableCell>{endIndex + 1}</TableCell>
|
||||
<TableCell>{end.arrows.join(', ')}</TableCell>
|
||||
<TableCell align="right">{end.total}</TableCell>
|
||||
<TableCell align="right">{runningTotal}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Grid>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Box>
|
||||
))
|
||||
|
||||
@@ -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,45 +50,8 @@ const ScoreTracker = () => {
|
||||
}
|
||||
}, [state.currentGame, navigate]);
|
||||
|
||||
// Update score distribution whenever the current game changes
|
||||
useEffect(() => {
|
||||
if (state.currentGame) {
|
||||
const distribution = calculateScoreDistribution();
|
||||
setScoreDistribution(distribution);
|
||||
setTotalArrows(calculateTotalArrows(distribution));
|
||||
}
|
||||
}, [state.currentGame]);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
// Calculate score distribution for all completed ends
|
||||
const calculateScoreDistribution = () => {
|
||||
const calculateScoreDistribution = useCallback(() => {
|
||||
// Initialize distribution object based on game type
|
||||
let distribution = {};
|
||||
|
||||
@@ -117,6 +79,43 @@ const ScoreTracker = () => {
|
||||
}
|
||||
|
||||
return distribution;
|
||||
}, [state.currentGame]);
|
||||
|
||||
// Update score distribution whenever the current game changes
|
||||
useEffect(() => {
|
||||
if (state.currentGame) {
|
||||
const distribution = calculateScoreDistribution();
|
||||
setScoreDistribution(distribution);
|
||||
setTotalArrows(calculateTotalArrows(distribution));
|
||||
}
|
||||
}, [state.currentGame, calculateScoreDistribution]);
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
// Calculate total arrows shot
|
||||
@@ -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 = () => {
|
||||
</Grid>
|
||||
{/* Only show Ends Completed for league games */}
|
||||
{!isPracticeGame && (
|
||||
<Grid item xs={6}>
|
||||
<Typography variant="body1">
|
||||
Ends Completed: {state.currentGame.ends?.length || 0}
|
||||
<Grid item xs={12}
|
||||
key={`ends-completed-${state.currentGame.ends?.length || 0}`}
|
||||
>
|
||||
<Typography variant="body1" fontWeight="medium">
|
||||
Ends Completed: {state.currentGame.ends?.length || 0} of {state.currentGame.gameType === '450' ? 16:12 }
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
@@ -580,14 +573,16 @@ const ScoreTracker = () => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isPracticeGame && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={handleEndGame}
|
||||
sx={{ mt: 3 }}
|
||||
>
|
||||
{isPracticeGame ? "End Practice" : "End Game"}
|
||||
End Practice
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user