diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-06-05 22:08:51 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-06-05 22:08:51 -0700 |
| commit | cfc9cd8c7770ddc8f151610acd177e54169e28d7 (patch) | |
| tree | fcdc6d1b06966bef5198320ccd42e5a2aa1eea48 | |
| parent | 16d92646308a65224784a9a9fdccd6f69d02955e (diff) | |
feat: historical stats tracking, chart
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 13 | ||||
| -rw-r--r-- | src/components/Chart/index.tsx | 139 | ||||
| -rw-r--r-- | src/components/Game/index.tsx | 28 | ||||
| -rw-r--r-- | src/components/Result/index.tsx | 17 | ||||
| -rw-r--r-- | src/index.tsx | 9 |
6 files changed, 202 insertions, 5 deletions
diff --git a/package.json b/package.json index 71b5f82..7b07c3e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "lodash": "^4.18.1", "prop-types": "^15.8.1", "react": "^19.2.7", + "react-chartjs-2": "^5.3.1", "react-dom": "^19.2.7", "react-icons": "^5.6.0", "react-is": "^19.2.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2e4a61..828f20b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,6 +52,9 @@ dependencies: react: specifier: ^19.2.7 version: 19.2.7 + react-chartjs-2: + specifier: ^5.3.1 + version: 5.3.1(chart.js@4.5.1)(react@19.2.7) react-dom: specifier: ^19.2.7 version: 19.2.7(react@19.2.7) @@ -2596,6 +2599,16 @@ packages: unpipe: 1.0.0 dev: false + /react-chartjs-2@5.3.1(chart.js@4.5.1)(react@19.2.7): + resolution: {integrity: sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==} + peerDependencies: + chart.js: ^4.1.1 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + chart.js: 4.5.1 + react: 19.2.7 + dev: false + /react-dom@19.2.7(react@19.2.7): resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} peerDependencies: diff --git a/src/components/Chart/index.tsx b/src/components/Chart/index.tsx new file mode 100644 index 0000000..6aac91e --- /dev/null +++ b/src/components/Chart/index.tsx @@ -0,0 +1,139 @@ +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + BarElement, + Tooltip, + Legend, +} from "chart.js"; +import type { ChartOptions } from "chart.js"; +import { Bar } from "react-chartjs-2"; + +ChartJS.register( + CategoryScale, + LinearScale, + BarElement, + Tooltip, + Legend +); + +interface Props { + currentTry: number; + didGuess: boolean; + sessionDate: string; +} + +interface HistoricalPlayData { + sessionDates?: string[]; + guesses?: number[]; + didGuess?: boolean[]; +} + +type GuessCount = 1 | 2 | 3 | 4 | 5 | 6; +type Distribution = Record<GuessCount, number> & { NS: number }; + +function getHistoricalPlayData(): HistoricalPlayData { + try { + return JSON.parse(localStorage.getItem("historicalPlayData") || "{}"); + } catch { + return {}; + } +} + +function addResultToDistribution( + distribution: Distribution, + guessCount: number, + didGuess: boolean +) { + if (!didGuess) { + distribution.NS += 1; + return; + } + + if (guessCount >= 1 && guessCount <= 6) { + distribution[guessCount as GuessCount] += 1; + } +} + +export default function GuessDistributionChart({ + currentTry, + didGuess, + sessionDate, +}: Props) { + const historicalPlayData = getHistoricalPlayData(); + + const distribution: Distribution = { + 1: 0, + 2: 0, + 3: 0, + 4: 0, + 5: 0, + 6: 0, + NS: 0, + }; + + if (historicalPlayData.guesses && historicalPlayData.didGuess) { + historicalPlayData.guesses.forEach((guessCount, index) => { + addResultToDistribution( + distribution, + guessCount, + historicalPlayData.didGuess?.[index] ?? false + ); + }); + } + + if (!historicalPlayData.sessionDates?.includes(sessionDate)) { + addResultToDistribution(distribution, currentTry, didGuess); + } + + const data = { + labels: ["1", "2", "3", "4", "5", "6", "NS"], + datasets: [ + { + data: [ + distribution[1], + distribution[2], + distribution[3], + distribution[4], + distribution[5], + distribution[6], + distribution.NS, + ], + backgroundColor: "#6aaa64", + borderRadius: 4, + }, + ], + }; + + const options: ChartOptions<"bar"> = { + responsive: true, + plugins: { + legend: { + display: false, + }, + tooltip: { + enabled: true, + }, + }, + scales: { + x: { + grid: { + display: false, + }, + }, + y: { + beginAtZero: true, + ticks: { + precision: 0, + }, + }, + }, + }; + + return ( + <div style={{ maxWidth: 500, width: "100%", marginBottom: "20px" }}> + <h3>Statistics</h3> + <Bar data={data} options={options} /> + </div> + ); +} diff --git a/src/components/Game/index.tsx b/src/components/Game/index.tsx index 59f9289..657b2af 100644 --- a/src/components/Game/index.tsx +++ b/src/components/Game/index.tsx @@ -47,13 +47,36 @@ export function Game({ !!dailyDate && !hasFinishedResponseDaily && new Date(getUtcDate()) > new Date(dailyDate); + const sessionDate = dailyDate ?? getUtcDate(); React.useEffect(() => { if (mode !== "daily") return; if (!hasFinishedCurrentRound) return; + localStorage.setItem("recentFinishedPlay", sessionDate); - localStorage.setItem("recentFinishedPlay", dailyDate ?? getUtcDate()); - }, [mode, hasFinishedCurrentRound, dailyDate]); + const historicalPlayData = localStorage.getItem("historicalPlayData"); + if (historicalPlayData === null) { + localStorage.setItem( + "historicalPlayData", + JSON.stringify({ + sessionDates: [sessionDate], + guesses: [currentTry], + didGuess: [didGuess], + }) + ); + } else { + const parsedData = JSON.parse(historicalPlayData); + if (parsedData.sessionDates.includes(sessionDate)) return; + localStorage.setItem( + "historicalPlayData", + JSON.stringify({ + sessionDates: [...parsedData.sessionDates, sessionDate], + guesses: [...parsedData.guesses, currentTry], + didGuess: [...parsedData.didGuess, didGuess], + }) + ); + } + }, [mode, hasFinishedCurrentRound, sessionDate, currentTry, didGuess]); if (isBlocked) { return <h1>Daily MIXX is not available yet. Check back soon!</h1>; @@ -67,6 +90,7 @@ export function Game({ todaysSolution={todaysSolution} guesses={guesses} mode={mode} + sessionDate={sessionDate} onPlayAgain={onPlayAgain} /> ); diff --git a/src/components/Result/index.tsx b/src/components/Result/index.tsx index 4ce7f35..c74e4f8 100644 --- a/src/components/Result/index.tsx +++ b/src/components/Result/index.tsx @@ -9,6 +9,7 @@ import { MiniYouTubePlayer } from "../MiniYouTubePlayer"; import * as Styled from "./index.styled"; import { theme } from "../../constants"; +import GuessDistributionChart from '../Chart'; interface SolutionProps { didGuess: boolean; @@ -89,6 +90,7 @@ interface Props { todaysSolution: Song; guesses: GuessType[]; mode?: "daily" | "unlimited"; + sessionDate: string; onPlayAgain?: () => void; } @@ -98,6 +100,7 @@ export function Result({ guesses, currentTry, mode = "daily", + sessionDate, onPlayAgain, }: Props) { const now = new Date(); @@ -126,6 +129,13 @@ export function Result({ currentTry={currentTry} isUnlimited={isUnlimited} /> + {!isUnlimited && ( + <GuessDistributionChart + currentTry={currentTry} + didGuess={didGuess} + sessionDate={sessionDate} + /> + )} {!isUnlimited && <ShareButton guesses={guesses} variant="green" />} @@ -152,6 +162,13 @@ export function Result({ currentTry={currentTry} isUnlimited={isUnlimited} /> + {!isUnlimited && ( + <GuessDistributionChart + currentTry={currentTry} + didGuess={didGuess} + sessionDate={sessionDate} + /> + )} {!isUnlimited && <ShareButton guesses={guesses} variant="red" />} diff --git a/src/index.tsx b/src/index.tsx index e1b6a22..c4a2bfc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,15 +1,18 @@ import React from "react"; -import ReactDOM from "react-dom"; +import ReactDOM from "react-dom/client"; import { ThemeProvider } from "styled-components"; import { theme } from "./constants"; import "./index.css"; import App from "./app"; -ReactDOM.render( +const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement +); + +root.render( <React.StrictMode> <ThemeProvider theme={theme}> <App /> </ThemeProvider> </React.StrictMode>, - document.getElementById("root") ); |
