Broken updates
This commit is contained in:
8
asc/package-lock.json
generated
8
asc/package-lock.json
generated
@@ -18562,9 +18562,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.7.3",
|
"version": "4.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -18572,7 +18572,7 @@
|
|||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.17"
|
"node": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
|
|||||||
@@ -1,33 +1,83 @@
|
|||||||
// src/App.js
|
// src/App.js
|
||||||
import React from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { CssBaseline, Container } from '@mui/material';
|
import { CssBaseline, Container, ThemeProvider, createTheme, IconButton } from '@mui/material';
|
||||||
import { ScoreProvider, useScore } from './context/ScoreContext';
|
import { Brightness4, Brightness7 } from '@mui/icons-material';
|
||||||
|
import { ScoreProvider } from './context/ScoreContext';
|
||||||
import GameSetup from './components/GameSetup';
|
import GameSetup from './components/GameSetup';
|
||||||
import ScoreTracker from './components/ScoreTracker';
|
import ScoreTracker from './components/ScoreTracker';
|
||||||
|
|
||||||
function AppContent() {
|
|
||||||
const { state } = useScore();
|
|
||||||
|
|
||||||
// Check if a game is in progress (i.e., a gameType is selected)
|
|
||||||
return (
|
|
||||||
<Container maxWidth="lg">
|
|
||||||
{state.currentGame.gameType ? (
|
|
||||||
<ScoreTracker />
|
|
||||||
) : (
|
|
||||||
<GameSetup />
|
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
// Initialize theme from localStorage or default to 'light'
|
||||||
|
const [mode, setMode] = useState(() => {
|
||||||
|
try {
|
||||||
|
const savedMode = localStorage.getItem('themeMode');
|
||||||
|
return savedMode || 'light';
|
||||||
|
} catch {
|
||||||
|
return 'light';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save theme preference to localStorage
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('themeMode', mode);
|
||||||
|
}, [mode]);
|
||||||
|
|
||||||
|
const theme = useMemo(
|
||||||
|
() =>
|
||||||
|
createTheme({
|
||||||
|
palette: {
|
||||||
|
mode,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[mode],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
setMode((prevMode) => (prevMode === 'light' ? 'dark' : 'light'));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
<ScoreProvider>
|
<ScoreProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<AppContent />
|
<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>
|
||||||
|
{!gameStarted ? (
|
||||||
|
<GameSetup onGameStart={() => setGameStarted(true)} />
|
||||||
|
) : (
|
||||||
|
<ScoreTracker />
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
</ScoreProvider>
|
</ScoreProvider>
|
||||||
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useScore, ACTIONS } from '../context/ScoreContext';
|
import { useScore, ACTIONS } from '../context/ScoreContext';
|
||||||
|
|
||||||
const GameSetup = () => {
|
const GameSetup = ({ onGameStart }) => {
|
||||||
const { dispatch } = useScore();
|
const { dispatch } = useScore();
|
||||||
|
|
||||||
const startGame = (gameType) => {
|
const startGame = (gameType) => {
|
||||||
@@ -18,6 +18,7 @@ const GameSetup = () => {
|
|||||||
type: ACTIONS.START_NEW_ROUND,
|
type: ACTIONS.START_NEW_ROUND,
|
||||||
payload: { gameType }
|
payload: { gameType }
|
||||||
});
|
});
|
||||||
|
onGameStart();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
81
asc/src/components/NavigationMenu.js
Normal file
81
asc/src/components/NavigationMenu.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// src/components/NavigationMenu.js
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
AppBar,
|
||||||
|
Toolbar,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
Drawer,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Divider,
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
Menu as MenuIcon,
|
||||||
|
Home as HomeIcon,
|
||||||
|
Assessment as AssessmentIcon,
|
||||||
|
EmojiEvents as LeagueIcon,
|
||||||
|
SportsMartialArts as PracticeIcon,
|
||||||
|
Settings as SettingsIcon,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import { useScore } from '../context/ScoreContext';
|
||||||
|
|
||||||
|
const NavigationMenu = ({ onNavigate }) => {
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const { state } = useScore();
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ title: 'Home', icon: <HomeIcon />, action: 'home' },
|
||||||
|
{ title: 'League Shoots', icon: <LeagueIcon />, action: 'league' },
|
||||||
|
{ title: 'Practice Shoots', icon: <PracticeIcon />, action: 'practice' },
|
||||||
|
{ title: 'Statistics', icon: <AssessmentIcon />, action: 'stats' },
|
||||||
|
{ title: 'Settings', icon: <SettingsIcon />, action: 'settings' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleNavigation = (action) => {
|
||||||
|
setDrawerOpen(false);
|
||||||
|
onNavigate(action);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AppBar position="static">
|
||||||
|
<Toolbar>
|
||||||
|
<IconButton
|
||||||
|
edge="start"
|
||||||
|
color="inherit"
|
||||||
|
onClick={() => setDrawerOpen(true)}
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h6" sx={{ flexGrow: 1 }}>
|
||||||
|
Archery Score Card
|
||||||
|
</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
anchor="left"
|
||||||
|
open={drawerOpen}
|
||||||
|
onClose={() => setDrawerOpen(false)}
|
||||||
|
>
|
||||||
|
<List sx={{ width: 250 }}>
|
||||||
|
{menuItems.map((item) => (
|
||||||
|
<ListItem
|
||||||
|
button
|
||||||
|
key={item.action}
|
||||||
|
onClick={() => handleNavigation(item.action)}
|
||||||
|
>
|
||||||
|
<ListItemIcon>{item.icon}</ListItemIcon>
|
||||||
|
<ListItemText primary={item.title} />
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavigationMenu;
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
// src/components/ScoreTracker.js
|
// src/components/ScoreTracker.js
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useScore, ACTIONS } from '../context/ScoreContext';
|
import { useScore, ACTIONS } from '../context/ScoreContext';
|
||||||
import { Button, Grid, Typography, TextField } from '@mui/material';
|
import { Button, Grid, Typography, TextField, Box } from '@mui/material';
|
||||||
|
|
||||||
const ScoreTracker = () => {
|
const ScoreTracker = () => {
|
||||||
const { state, dispatch } = useScore();
|
const { state, dispatch } = useScore();
|
||||||
const [arrowScores, setArrowScores] = useState(['', '', '', '', '']); // Track scores for each arrow
|
const [arrowScores, setArrowScores] = useState(['', '', '', '', '']);
|
||||||
|
|
||||||
const gameType = state.currentGame.gameType;
|
const gameType = state.currentGame.gameType;
|
||||||
const maxArrowsPerRound = gameType === '450' ? 3 : 5; // 3 for 450 game, 5 for 300 game
|
const maxArrowsPerRound = gameType === '450' ? 3 : 5;
|
||||||
const maxScore = gameType === '450' ? 10 : 5; // Max score per arrow depends on the game
|
const maxScore = gameType === '450' ? 10 : 5;
|
||||||
|
|
||||||
// Handle arrow score input change
|
|
||||||
const handleScoreChange = (index, value) => {
|
const handleScoreChange = (index, value) => {
|
||||||
const updatedScores = [...arrowScores];
|
const updatedScores = [...arrowScores];
|
||||||
updatedScores[index] = value;
|
updatedScores[index] = value;
|
||||||
@@ -19,7 +17,6 @@ const ScoreTracker = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddRound = () => {
|
const handleAddRound = () => {
|
||||||
// Validate all scores: numeric between 0 and maxScore, or 'X'
|
|
||||||
const valid = arrowScores.slice(0, maxArrowsPerRound).every(score =>
|
const valid = arrowScores.slice(0, maxArrowsPerRound).every(score =>
|
||||||
(score >= 0 && score <= maxScore) || score.toUpperCase() === 'X'
|
(score >= 0 && score <= maxScore) || score.toUpperCase() === 'X'
|
||||||
);
|
);
|
||||||
@@ -28,86 +25,145 @@ const ScoreTracker = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch each arrow score for the round
|
|
||||||
arrowScores.slice(0, maxArrowsPerRound).forEach((score) => {
|
arrowScores.slice(0, maxArrowsPerRound).forEach((score) => {
|
||||||
const arrowScore = score.toUpperCase() === 'X' ? maxScore : parseInt(score, 10);
|
const arrowScore = score.toUpperCase() === 'X' ? maxScore : parseInt(score, 10);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.ADD_ARROW,
|
type: ACTIONS.ADD_ARROW,
|
||||||
payload: {
|
payload: {
|
||||||
roundIndex: state.currentGame.rounds.length, // Current round index
|
roundIndex: state.currentGame.rounds.length,
|
||||||
score: arrowScore,
|
score: arrowScore,
|
||||||
isBullseye: score.toUpperCase() === 'X',
|
isBullseye: score.toUpperCase() === 'X',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset the arrow scores for the next round
|
|
||||||
setArrowScores(['', '', '', '', '']);
|
setArrowScores(['', '', '', '', '']);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={3} justifyContent="center" alignItems="center" style={{ minHeight: '80vh' }}>
|
<Grid container spacing={2} justifyContent="center">
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h4" align="center" gutterBottom>
|
<Typography variant="h5" align="center" gutterBottom>
|
||||||
Score Tracker: {gameType} Round
|
{gameType} Round - Round {state.currentGame.rounds.length + 1}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
{/* Compact score input section */}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h6" align="center">
|
<Box
|
||||||
Round {state.currentGame.rounds.length + 1}
|
sx={{
|
||||||
</Typography>
|
display: 'flex',
|
||||||
</Grid>
|
justifyContent: 'center',
|
||||||
|
gap: 1,
|
||||||
{/* Arrow score inputs */}
|
mb: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
{Array.from({ length: maxArrowsPerRound }).map((_, index) => (
|
{Array.from({ length: maxArrowsPerRound }).map((_, index) => (
|
||||||
<Grid item xs={12} sm={6} key={index}>
|
|
||||||
<TextField
|
<TextField
|
||||||
label={`Arrow ${index + 1} Score`}
|
key={index}
|
||||||
value={arrowScores[index]}
|
value={arrowScores[index]}
|
||||||
onChange={(e) => handleScoreChange(index, e.target.value)}
|
onChange={(e) => handleScoreChange(index, e.target.value)}
|
||||||
fullWidth
|
placeholder="0"
|
||||||
placeholder={`Enter 0-${maxScore} or X`}
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: '60px',
|
||||||
|
'& .MuiInputBase-input': {
|
||||||
|
padding: '8px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
inputProps={{
|
||||||
|
maxLength: 1,
|
||||||
|
style: { textAlign: 'center' }
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
|
||||||
))}
|
))}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
{/* Add round button */}
|
{/* Score buttons */}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Button variant="contained" color="primary" onClick={handleAddRound} fullWidth>
|
<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
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
onClick={handleAddRound}
|
||||||
|
disabled={!arrowScores.slice(0, maxArrowsPerRound).every(score => score !== '')}
|
||||||
|
>
|
||||||
Add Round
|
Add Round
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => setArrowScores(['', '', '', '', ''])}
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Current game status */}
|
{/* Scores display */}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h6" align="center">
|
<Typography variant="h6" align="center">
|
||||||
Total Score: {state.currentGame.totalScore}
|
Total Score: {state.currentGame.totalScore} |
|
||||||
</Typography>
|
Bullseyes: {state.currentGame.totalBullseyes}
|
||||||
<Typography variant="h6" align="center">
|
|
||||||
Total Bullseyes: {state.currentGame.totalBullseyes}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{/* Display all round scores */}
|
{/* Round history */}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="h6" align="center" gutterBottom>
|
<Box sx={{ maxHeight: '200px', overflow: 'auto' }}>
|
||||||
All Round Scores:
|
|
||||||
</Typography>
|
|
||||||
<Grid container spacing={2}>
|
|
||||||
{state.currentGame.rounds.map((round, roundIndex) => (
|
{state.currentGame.rounds.map((round, roundIndex) => (
|
||||||
<Grid item xs={12} key={roundIndex}>
|
<Typography key={roundIndex} variant="body2" align="center">
|
||||||
<Typography variant="body1" align="center">
|
Round {roundIndex + 1}: {round.arrows.join(', ')}
|
||||||
Round {roundIndex + 1}: {round.arrows.join(', ')} (Total: {round.total}, Bullseyes: {round.bullseyes})
|
(Total: {round.total}, Bullseyes: {round.bullseyes})
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ScoreTracker;
|
export default ScoreTracker;
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,25 @@
|
|||||||
// src/context/ScoreContext.js
|
// src/context/ScoreContext.js
|
||||||
import React, { createContext, useContext, useReducer, useEffect } from 'react';
|
import React, { createContext, useContext, useReducer, useEffect } from 'react';
|
||||||
|
|
||||||
// Define action types
|
|
||||||
export const ACTIONS = {
|
export const ACTIONS = {
|
||||||
ADD_ARROW: 'ADD_ARROW',
|
ADD_ARROW: 'ADD_ARROW',
|
||||||
START_NEW_ROUND: 'START_NEW_ROUND',
|
START_NEW_ROUND: 'START_NEW_ROUND',
|
||||||
RESET_GAME: 'RESET_GAME',
|
RESET_GAME: 'RESET_GAME',
|
||||||
};
|
LOAD_SAVED_GAME: 'LOAD_SAVED_GAME',
|
||||||
|
START_NEW_GAME: 'START_NEW_GAME',
|
||||||
// Initial state structure
|
SAVE_GAME: 'SAVE_GAME',
|
||||||
const initialState = {
|
UPDATE_HANDICAP: 'UPDATE_HANDICAP'
|
||||||
currentGame: {
|
|
||||||
gameType: null, // '450' or '300'
|
|
||||||
rounds: [], // Array of rounds
|
|
||||||
totalScore: 0,
|
|
||||||
totalBullseyes: 0,
|
|
||||||
dateStarted: null,
|
|
||||||
},
|
|
||||||
games: [], // Historical games
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reducer function
|
// Reducer function
|
||||||
const scoreReducer = (state, action) => {
|
const scoreReducer = (state, action) => {
|
||||||
|
let newState;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ACTIONS.ADD_ARROW:
|
case ACTIONS.ADD_ARROW:
|
||||||
const { roundIndex, score, isBullseye } = action.payload;
|
const { roundIndex, score, isBullseye } = action.payload;
|
||||||
const updatedRounds = [...state.currentGame.rounds];
|
const updatedRounds = [...state.currentGame.rounds];
|
||||||
|
|
||||||
// Update the specific round
|
|
||||||
if (!updatedRounds[roundIndex]) {
|
if (!updatedRounds[roundIndex]) {
|
||||||
updatedRounds[roundIndex] = { arrows: [], total: 0, bullseyes: 0 };
|
updatedRounds[roundIndex] = { arrows: [], total: 0, bullseyes: 0 };
|
||||||
}
|
}
|
||||||
@@ -39,11 +31,10 @@ const scoreReducer = (state, action) => {
|
|||||||
bullseyes: updatedRounds[roundIndex].bullseyes + (isBullseye ? 1 : 0),
|
bullseyes: updatedRounds[roundIndex].bullseyes + (isBullseye ? 1 : 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate new totals
|
|
||||||
const totalScore = updatedRounds.reduce((sum, round) => sum + round.total, 0);
|
const totalScore = updatedRounds.reduce((sum, round) => sum + round.total, 0);
|
||||||
const totalBullseyes = updatedRounds.reduce((sum, round) => sum + round.bullseyes, 0);
|
const totalBullseyes = updatedRounds.reduce((sum, round) => sum + round.bullseyes, 0);
|
||||||
|
|
||||||
return {
|
newState = {
|
||||||
...state,
|
...state,
|
||||||
currentGame: {
|
currentGame: {
|
||||||
...state.currentGame,
|
...state.currentGame,
|
||||||
@@ -52,29 +43,41 @@ const scoreReducer = (state, action) => {
|
|||||||
totalBullseyes,
|
totalBullseyes,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
case ACTIONS.START_NEW_ROUND:
|
case ACTIONS.START_NEW_ROUND:
|
||||||
const { gameType } = action.payload;
|
// If there's a current game, save it to history
|
||||||
return {
|
const gamesHistory = state.currentGame.gameType ?
|
||||||
...state,
|
[...state.games, state.currentGame] :
|
||||||
|
state.games;
|
||||||
|
|
||||||
|
newState = {
|
||||||
|
games: gamesHistory,
|
||||||
currentGame: {
|
currentGame: {
|
||||||
gameType,
|
gameType: action.payload.gameType,
|
||||||
rounds: [],
|
rounds: [],
|
||||||
totalScore: 0,
|
totalScore: 0,
|
||||||
totalBullseyes: 0,
|
totalBullseyes: 0,
|
||||||
dateStarted: new Date().toISOString(),
|
dateStarted: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
case ACTIONS.RESET_GAME:
|
case ACTIONS.RESET_GAME:
|
||||||
return {
|
newState = initialState;
|
||||||
...state,
|
break;
|
||||||
currentGame: initialState.currentGame,
|
|
||||||
};
|
case ACTIONS.LOAD_SAVED_GAME:
|
||||||
|
newState = action.payload;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save to localStorage after every state change
|
||||||
|
localStorage.setItem('archeryScores', JSON.stringify(newState));
|
||||||
|
return newState;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create context
|
// Create context
|
||||||
@@ -84,15 +87,21 @@ const ScoreContext = createContext();
|
|||||||
export const ScoreProvider = ({ children }) => {
|
export const ScoreProvider = ({ children }) => {
|
||||||
// Load state from localStorage on initial render
|
// Load state from localStorage on initial render
|
||||||
const [state, dispatch] = useReducer(scoreReducer, initialState, () => {
|
const [state, dispatch] = useReducer(scoreReducer, initialState, () => {
|
||||||
|
try {
|
||||||
const localData = localStorage.getItem('archeryScores');
|
const localData = localStorage.getItem('archeryScores');
|
||||||
return localData ? JSON.parse(localData) : initialState;
|
if (localData) {
|
||||||
|
const parsedData = JSON.parse(localData);
|
||||||
|
// Verify the data structure
|
||||||
|
if (parsedData.currentGame && parsedData.games) {
|
||||||
|
return parsedData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading saved game:', error);
|
||||||
|
}
|
||||||
|
return initialState;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save to localStorage whenever state changes
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem('archeryScores', JSON.stringify(state));
|
|
||||||
}, [state]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScoreContext.Provider value={{ state, dispatch }}>
|
<ScoreContext.Provider value={{ state, dispatch }}>
|
||||||
{children}
|
{children}
|
||||||
@@ -108,3 +117,122 @@ export const useScore = () => {
|
|||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// src/context/ScoreContext.js
|
||||||
|
// ... (keeping existing imports and initial setup)
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
currentGame: {
|
||||||
|
id: null,
|
||||||
|
gameType: null, // '450' or '300'
|
||||||
|
category: null, // 'league' or 'practice'
|
||||||
|
rounds: [],
|
||||||
|
totalScore: 0,
|
||||||
|
totalBullseyes: 0,
|
||||||
|
dateStarted: null,
|
||||||
|
dateCompleted: null,
|
||||||
|
handicap: null,
|
||||||
|
},
|
||||||
|
games: {
|
||||||
|
league: [],
|
||||||
|
practice: [],
|
||||||
|
},
|
||||||
|
statistics: {
|
||||||
|
handicapHistory: [],
|
||||||
|
averageScores: {
|
||||||
|
league: { '450': 0, '300': 0 },
|
||||||
|
practice: { '450': 0, '300': 0 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handicap calculation function (basic version - can be adjusted based on your league's rules)
|
||||||
|
const calculateHandicap = (scores) => {
|
||||||
|
if (scores.length < 3) return null;
|
||||||
|
|
||||||
|
// Take the average of the last 3 scores
|
||||||
|
const lastThree = scores.slice(-3);
|
||||||
|
const average = lastThree.reduce((sum, game) => sum + game.totalScore, 0) / 3;
|
||||||
|
|
||||||
|
// Example handicap calculation (adjust formula as needed)
|
||||||
|
const maxPossibleScore = scores[0].gameType === '450' ? 450 : 300;
|
||||||
|
const handicap = Math.round((maxPossibleScore - average) * 0.8);
|
||||||
|
|
||||||
|
return Math.max(0, Math.min(100, handicap)); // Cap between 0 and 100
|
||||||
|
};
|
||||||
|
|
||||||
|
const scoreReducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ACTIONS.START_NEW_GAME:
|
||||||
|
const { gameType, category } = action.payload;
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentGame: {
|
||||||
|
...initialState.currentGame,
|
||||||
|
id: Date.now(),
|
||||||
|
gameType,
|
||||||
|
category,
|
||||||
|
dateStarted: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
case ACTIONS.SAVE_GAME:
|
||||||
|
const completedGame = {
|
||||||
|
...state.currentGame,
|
||||||
|
dateCompleted: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate handicap for league games
|
||||||
|
if (completedGame.category === 'league') {
|
||||||
|
const relevantGames = [...state.games.league, completedGame]
|
||||||
|
.filter(game => game.gameType === completedGame.gameType)
|
||||||
|
.sort((a, b) => new Date(b.dateCompleted) - new Date(a.dateCompleted));
|
||||||
|
|
||||||
|
completedGame.handicap = calculateHandicap(relevantGames);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newGames = {
|
||||||
|
...state.games,
|
||||||
|
[completedGame.category]: [
|
||||||
|
...state.games[completedGame.category],
|
||||||
|
completedGame,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update statistics
|
||||||
|
const updateAverages = (games, type, category) => {
|
||||||
|
const relevantGames = games.filter(game => game.gameType === type);
|
||||||
|
return relevantGames.length > 0
|
||||||
|
? relevantGames.reduce((sum, game) => sum + game.totalScore, 0) / relevantGames.length
|
||||||
|
: 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const newStatistics = {
|
||||||
|
...state.statistics,
|
||||||
|
averageScores: {
|
||||||
|
league: {
|
||||||
|
'450': updateAverages(newGames.league, '450', 'league'),
|
||||||
|
'300': updateAverages(newGames.league, '300', 'league'),
|
||||||
|
},
|
||||||
|
practice: {
|
||||||
|
'450': updateAverages(newGames.practice, '450', 'practice'),
|
||||||
|
'300': updateAverages(newGames.practice, '300', 'practice'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentGame: initialState.currentGame,
|
||||||
|
games: newGames,
|
||||||
|
statistics: newStatistics,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ... (existing cases)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ... (rest of the context implementation)
|
||||||
|
|||||||
Reference in New Issue
Block a user