From 854f88c43ae30604246bc41951f860ea39416c94 Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 22 Oct 2024 13:54:54 +0100 Subject: [PATCH] track total score, total ranked score, replay watched count and add a score chart --- .../backend/src/service/player.service.ts | 17 ++- .../src/player/impl/scoresaber-player.ts | 24 ++-- projects/common/src/player/player-history.ts | 20 +++ .../common/src/player/player-stat-change.ts | 64 ++++++++++ projects/common/src/player/player-stat.ts | 32 ----- projects/common/src/player/player.ts | 6 +- projects/common/src/player/stat-timeframe.ts | 1 - .../website/src/components/navbar/navbar.tsx | 5 +- .../{ => charts}/player-accuracy-chart.tsx | 0 .../chart/charts/player-ranking-chart.tsx | 67 ++++++++++ .../player-scores-chart.tsx} | 116 ++++++------------ .../components/player/chart/player-charts.tsx | 12 +- .../src/components/player/player-header.tsx | 14 +-- .../src/components/player/player-stats.tsx | 38 ++++-- .../components/statistic/change-over-time.tsx | 25 ++-- .../src/components/statistic/daily-change.tsx | 34 ++--- 16 files changed, 287 insertions(+), 188 deletions(-) create mode 100644 projects/common/src/player/player-stat-change.ts delete mode 100644 projects/common/src/player/player-stat.ts delete mode 100644 projects/common/src/player/stat-timeframe.ts rename projects/website/src/components/player/chart/{ => charts}/player-accuracy-chart.tsx (100%) create mode 100644 projects/website/src/components/player/chart/charts/player-ranking-chart.tsx rename projects/website/src/components/player/chart/{player-ranking-chart.tsx => charts/player-scores-chart.tsx} (58%) diff --git a/projects/backend/src/service/player.service.ts b/projects/backend/src/service/player.service.ts index 9f61291..dd6ed6d 100644 --- a/projects/backend/src/service/player.service.ts +++ b/projects/backend/src/service/player.service.ts @@ -148,6 +148,7 @@ export class PlayerService { history.countryRank = player.countryRank; history.rank = player.rank; history.accuracy = { + ...history.accuracy, averageRankedAccuracy: scoreStats.averageRankedAccuracy, }; history.scores = { @@ -155,6 +156,11 @@ export class PlayerService { totalScores: scoreStats.totalPlayCount, totalRankedScores: scoreStats.rankedPlayCount, }; + history.score = { + ...history.score, + totalScore: scoreStats.totalScore, + totalRankedScore: scoreStats.totalRankedScore, + }; foundPlayer.setStatisticHistory(dateToday, history); foundPlayer.sortStatisticHistory(); @@ -181,12 +187,11 @@ export class PlayerService { } const today = new Date(); - let history = player.getHistoryByDate(today); - if (history == undefined || Object.keys(history).length === 0) { - history = { scores: { rankedScores: 0, unrankedScores: 0 } }; // Ensure initialization - } - - const scores = history.scores || {}; + const history = player.getHistoryByDate(today); + const scores = history.scores || { + rankedScores: 0, + unrankedScores: 0, + }; if (leaderboard.stars > 0) { scores.rankedScores!++; } else { diff --git a/projects/common/src/player/impl/scoresaber-player.ts b/projects/common/src/player/impl/scoresaber-player.ts index dd2ced8..8c6bdb7 100644 --- a/projects/common/src/player/impl/scoresaber-player.ts +++ b/projects/common/src/player/impl/scoresaber-player.ts @@ -110,18 +110,11 @@ export async function getScoreSaberPlayerFromToken( if (history) { // Use the latest data for today history[todayDate] = { - ...{ - scores: { - rankedScores: 0, - unrankedScores: 0, - totalScores: 0, - totalRankedScores: 0, - }, - }, ...history[todayDate], rank: token.rank, countryRank: token.countryRank, pp: token.pp, + replaysWatched: token.scoreStats.replaysWatched, accuracy: { averageRankedAccuracy: token.scoreStats.averageRankedAccuracy, }, @@ -129,6 +122,10 @@ export async function getScoreSaberPlayerFromToken( totalScores: token.scoreStats.totalPlayCount, totalRankedScores: token.scoreStats.rankedPlayCount, }, + score: { + totalScore: token.scoreStats.totalScore, + totalRankedScore: token.scoreStats.totalRankedScore, + }, }; isBeingTracked = true; @@ -158,10 +155,6 @@ export async function getScoreSaberPlayerFromToken( statisticHistory[dateKey] = { ...statisticHistory[dateKey], rank: rank, - scores: { - totalScores: token.scoreStats.totalPlayCount, - totalRankedScores: token.scoreStats.rankedPlayCount, - }, }; } } @@ -247,9 +240,15 @@ export async function getScoreSaberPlayerFromToken( rank: getStatisticChange("rank", true, daysAgo), countryRank: getStatisticChange("countryRank", true, daysAgo), pp: getStatisticChange("pp", false, daysAgo), + replaysWatched: getStatisticChange("replaysWatched", false, daysAgo), + accuracy: { + averageRankedAccuracy: getStatisticChange("accuracy.averageRankedAccuracy", false, daysAgo), + }, scores: { totalScores: getStatisticChange("scores.totalScores", false, daysAgo), totalRankedScores: getStatisticChange("scores.totalRankedScores", false, daysAgo), + rankedScores: getStatisticChange("scores.rankedScores", false, daysAgo), + unrankedScores: getStatisticChange("scores.unrankedScores", false, daysAgo), }, }; }; @@ -268,7 +267,6 @@ export async function getScoreSaberPlayerFromToken( daily: getStatisticChanges(1), weekly: getStatisticChanges(7), monthly: getStatisticChanges(30), - yearly: getStatisticChanges(365), }, role: token.role == null ? undefined : token.role, badges: badges, diff --git a/projects/common/src/player/player-history.ts b/projects/common/src/player/player-history.ts index b928166..d9f4505 100644 --- a/projects/common/src/player/player-history.ts +++ b/projects/common/src/player/player-history.ts @@ -14,6 +14,26 @@ export interface PlayerHistory { */ pp?: number; + /** + * How many times replays of the player scores have been watched + */ + replaysWatched?: number; + + /** + * The player's score stats. + */ + score?: { + /** + * The total amount of unranked and ranked score. + */ + totalScore?: number; + + /** + * The total amount of ranked score. + */ + totalRankedScore?: number; + }; + /** * The amount of scores set for this day. */ diff --git a/projects/common/src/player/player-stat-change.ts b/projects/common/src/player/player-stat-change.ts new file mode 100644 index 0000000..e463e43 --- /dev/null +++ b/projects/common/src/player/player-stat-change.ts @@ -0,0 +1,64 @@ +import ScoreSaberPlayer from "@ssr/player/impl/scoresaber-player"; +import { ChangeRange } from "@ssr/player/player"; + +export type PlayerStatValue = { + /** + * The type of the stat. + */ + type: string; + + /** + * The value of the stat. + */ + value: (player: ScoreSaberPlayer, range: ChangeRange) => number | undefined; +}; + +export type PlayerStatChangeType = + | "Rank" + | "CountryRank" + | "PerformancePoints" + | "TotalPlayCount" + | "RankedPlayCount" + | "TotalScore" + | "TotalRankedScore" + | "AverageRankedAccuracy" + | "TotalReplaysWatched"; + +export const PlayerStatChange: Record = { + Rank: { + type: "Rank", + value: (player, range) => player.statisticChange?.[range].rank, + }, + CountryRank: { + type: "Country Rank", + value: (player, range) => player.statisticChange?.[range].countryRank, + }, + PerformancePoints: { + type: "Performance Points", + value: (player, range) => player.statisticChange?.[range].pp, + }, + TotalPlayCount: { + type: "Total Play Count", + value: (player, range) => player.statisticChange?.[range].scores?.totalScores, + }, + RankedPlayCount: { + type: "Ranked Play Count", + value: (player, range) => player.statisticChange?.[range].scores?.totalRankedScores, + }, + TotalScore: { + type: "Total Score", + value: (player, range) => player.statisticChange?.[range].score?.totalScore, + }, + TotalRankedScore: { + type: "Total Ranked Score", + value: (player, range) => player.statisticChange?.[range].scores?.totalRankedScores, + }, + AverageRankedAccuracy: { + type: "Average Ranked Accuracy", + value: (player, range) => player.statisticChange?.[range].accuracy?.averageRankedAccuracy, + }, + TotalReplaysWatched: { + type: "Total Replays Watched", + value: (player, range) => player.statisticChange?.[range].replaysWatched, + }, +}; diff --git a/projects/common/src/player/player-stat.ts b/projects/common/src/player/player-stat.ts deleted file mode 100644 index d904b9f..0000000 --- a/projects/common/src/player/player-stat.ts +++ /dev/null @@ -1,32 +0,0 @@ -export type PlayerStatValue = { - /** - * The display name of the stat. - */ - displayName: string; - - /** - * The value of the stat. - */ - value?: "rank" | "countryRank" | "pp"; -}; - -export const PlayerStat: Record = { - Rank: { - displayName: "Rank", - value: "rank", - }, - CountryRank: { - displayName: "Country Rank", - value: "countryRank", - }, - PerformancePoints: { - displayName: "Performance Points", - value: "pp", - }, - TotalPlayCount: { - displayName: "Total Play Count", - }, - RankedPlayCount: { - displayName: "Ranked Play Count", - }, -}; diff --git a/projects/common/src/player/player.ts b/projects/common/src/player/player.ts index e928872..41532e4 100644 --- a/projects/common/src/player/player.ts +++ b/projects/common/src/player/player.ts @@ -55,9 +55,7 @@ export default class Player { } } +export type ChangeRange = "daily" | "weekly" | "monthly"; export type StatisticChange = { - daily: PlayerHistory; - weekly: PlayerHistory; - monthly: PlayerHistory; - yearly: PlayerHistory; + [key in ChangeRange]: PlayerHistory; }; diff --git a/projects/common/src/player/stat-timeframe.ts b/projects/common/src/player/stat-timeframe.ts deleted file mode 100644 index 5c14785..0000000 --- a/projects/common/src/player/stat-timeframe.ts +++ /dev/null @@ -1 +0,0 @@ -export type StatTimeframe = "daily" | "weekly" | "monthly"; diff --git a/projects/website/src/components/navbar/navbar.tsx b/projects/website/src/components/navbar/navbar.tsx index b2f972b..172a113 100644 --- a/projects/website/src/components/navbar/navbar.tsx +++ b/projects/website/src/components/navbar/navbar.tsx @@ -4,8 +4,9 @@ import Link from "next/link"; import React from "react"; import NavbarButton from "./navbar-button"; import ProfileButton from "./profile-button"; -import { SwordIcon, TrendingUpIcon } from "lucide-react"; +import { TrendingUpIcon } from "lucide-react"; import FriendsButton from "@/components/navbar/friends-button"; +import { PiSwordFill } from "react-icons/pi"; type NavbarItem = { name: string; @@ -25,7 +26,7 @@ const items: NavbarItem[] = [ name: "Score Feed", link: "/scores", align: "left", - icon: , + icon: , }, { name: "Search", diff --git a/projects/website/src/components/player/chart/player-accuracy-chart.tsx b/projects/website/src/components/player/chart/charts/player-accuracy-chart.tsx similarity index 100% rename from projects/website/src/components/player/chart/player-accuracy-chart.tsx rename to projects/website/src/components/player/chart/charts/player-accuracy-chart.tsx diff --git a/projects/website/src/components/player/chart/charts/player-ranking-chart.tsx b/projects/website/src/components/player/chart/charts/player-ranking-chart.tsx new file mode 100644 index 0000000..291ce25 --- /dev/null +++ b/projects/website/src/components/player/chart/charts/player-ranking-chart.tsx @@ -0,0 +1,67 @@ +"use client"; + +import React from "react"; +import { DatasetConfig } from "@/components/chart/generic-chart"; +import GenericPlayerChart from "@/components/player/chart/generic-player-chart"; +import { scoreBarsDataset } from "@/components/player/chart/charts/player-scores-chart"; +import ScoreSaberPlayer from "@ssr/common/player/impl/scoresaber-player"; +import { formatNumberWithCommas, isWholeNumber } from "@ssr/common/utils/number-utils"; + +type Props = { + player: ScoreSaberPlayer; +}; + +// Dataset configuration for the chart +const datasetConfig: DatasetConfig[] = [ + { + title: "Rank", + field: "rank", + color: "#3EC1D3", + axisId: "y", + axisConfig: { + reverse: true, + display: true, + displayName: "Rank", + position: "left", + }, + labelFormatter: (value: number) => `Rank: #${formatNumberWithCommas(value)}`, + }, + { + title: "Country Rank", + field: "countryRank", + color: "#FFEA00", + axisId: "y1", + axisConfig: { + reverse: true, + display: false, + displayName: "Country Rank", + position: "left", + }, + labelFormatter: (value: number) => `Country Rank: #${formatNumberWithCommas(value)}`, + }, + { + title: "PP", + field: "pp", + color: "#4858ff", + axisId: "y2", + axisConfig: { + reverse: false, + display: true, + hideOnMobile: true, + displayName: "PP", + position: "right", + valueFormatter: value => { + if (isWholeNumber(value)) { + return value.toString(); + } + return value.toFixed(1); + }, + }, + labelFormatter: (value: number) => `PP: ${formatNumberWithCommas(value)}pp`, + }, + ...scoreBarsDataset, +]; + +export default function PlayerRankingChart({ player }: Props) { + return ; +} diff --git a/projects/website/src/components/player/chart/player-ranking-chart.tsx b/projects/website/src/components/player/chart/charts/player-scores-chart.tsx similarity index 58% rename from projects/website/src/components/player/chart/player-ranking-chart.tsx rename to projects/website/src/components/player/chart/charts/player-scores-chart.tsx index a9c5c70..f9c12f6 100644 --- a/projects/website/src/components/player/chart/player-ranking-chart.tsx +++ b/projects/website/src/components/player/chart/charts/player-scores-chart.tsx @@ -1,96 +1,21 @@ "use client"; -import { formatNumberWithCommas, isWholeNumber } from "@ssr/common/utils/number-utils"; import React from "react"; import { DatasetConfig } from "@/components/chart/generic-chart"; import GenericPlayerChart from "@/components/player/chart/generic-player-chart"; import ScoreSaberPlayer from "@ssr/common/player/impl/scoresaber-player"; +import { formatNumberWithCommas } from "@ssr/common/utils/number-utils"; type Props = { player: ScoreSaberPlayer; }; -// Dataset configuration for the chart -const datasetConfig: DatasetConfig[] = [ - { - title: "Rank", - field: "rank", - color: "#3EC1D3", - axisId: "y", - axisConfig: { - reverse: true, - display: true, - displayName: "Rank", - position: "left", - }, - labelFormatter: (value: number) => `Rank: #${formatNumberWithCommas(value)}`, - }, - { - title: "Country Rank", - field: "countryRank", - color: "#FFEA00", - axisId: "y1", - axisConfig: { - reverse: true, - display: false, - displayName: "Country Rank", - position: "left", - }, - labelFormatter: (value: number) => `Country Rank: #${formatNumberWithCommas(value)}`, - }, - { - title: "PP", - field: "pp", - color: "#4858ff", - axisId: "y2", - axisConfig: { - reverse: false, - display: true, - hideOnMobile: true, - displayName: "PP", - position: "right", - valueFormatter: value => { - if (isWholeNumber(value)) { - return value.toString(); - } - return value.toFixed(1); - }, - }, - labelFormatter: (value: number) => `PP: ${formatNumberWithCommas(value)}pp`, - }, - { - title: "Total Scores", - field: "scores.totalScores", - color: "#616161", - axisId: "y3", - showLegend: false, - axisConfig: { - reverse: false, - display: false, - displayName: "Total Scores", - position: "left", - }, - labelFormatter: (value: number) => `Total Scores: ${formatNumberWithCommas(value)}`, - }, - { - title: "Total Ranked Scores", - field: "scores.totalRankedScores", - color: "#6773ff", - axisId: "y4", - showLegend: false, - axisConfig: { - reverse: false, - display: false, - displayName: "Total Ranked Scores", - position: "left", - }, - labelFormatter: (value: number) => `Total Ranked Scores: ${formatNumberWithCommas(value)}`, - }, +export const scoreBarsDataset: DatasetConfig[] = [ { title: "Ranked Scores", field: "scores.rankedScores", color: "#ffae4d", - axisId: "y5", + axisId: "y100", axisConfig: { reverse: false, display: false, @@ -104,7 +29,7 @@ const datasetConfig: DatasetConfig[] = [ title: "Unranked Scores", field: "scores.unrankedScores", color: "#616161", - axisId: "y5", + axisId: "y100", axisConfig: { reverse: false, display: false, @@ -116,6 +41,37 @@ const datasetConfig: DatasetConfig[] = [ }, ]; -export default function PlayerRankingChart({ player }: Props) { +// Dataset configuration for the chart +const datasetConfig: DatasetConfig[] = [ + ...scoreBarsDataset, + { + title: "Total Scores", + field: "scores.totalScores", + color: "#616161", + axisId: "y1", + axisConfig: { + reverse: false, + display: true, + displayName: "Total Scores", + position: "left", + }, + labelFormatter: (value: number) => `Total Scores: ${formatNumberWithCommas(value)}`, + }, + { + title: "Total Ranked Scores", + field: "scores.totalRankedScores", + color: "#6773ff", + axisId: "y2", + axisConfig: { + reverse: false, + display: true, + displayName: "Total Ranked Scores", + position: "right", + }, + labelFormatter: (value: number) => `Total Ranked Scores: ${formatNumberWithCommas(value)}`, + }, +]; + +export default function PlayerScoresChart({ player }: Props) { return ; } diff --git a/projects/website/src/components/player/chart/player-charts.tsx b/projects/website/src/components/player/chart/player-charts.tsx index b65227d..d3a56c5 100644 --- a/projects/website/src/components/player/chart/player-charts.tsx +++ b/projects/website/src/components/player/chart/player-charts.tsx @@ -1,12 +1,14 @@ "use client"; -import PlayerRankingChart from "@/components/player/chart/player-ranking-chart"; +import PlayerRankingChart from "@/components/player/chart/charts/player-ranking-chart"; import { FC, useState } from "react"; import Tooltip from "@/components/tooltip"; -import PlayerAccuracyChart from "@/components/player/chart/player-accuracy-chart"; +import PlayerAccuracyChart from "@/components/player/chart/charts/player-accuracy-chart"; import { GlobeAmericasIcon } from "@heroicons/react/24/solid"; import { TrendingUpIcon } from "lucide-react"; import ScoreSaberPlayer from "@ssr/common/player/impl/scoresaber-player"; +import PlayerScoresChart from "@/components/player/chart/charts/player-scores-chart"; +import { PiSwordFill } from "react-icons/pi"; type PlayerChartsProps = { /** @@ -53,6 +55,12 @@ export default function PlayerCharts({ player }: PlayerChartsProps) { icon: , chart: PlayerAccuracyChart, }); + charts.push({ + index: 2, + label: "Scores", + icon: , + chart: PlayerScoresChart, + }); } const [selectedChart, setSelectedChart] = useState(charts[0]); diff --git a/projects/website/src/components/player/player-header.tsx b/projects/website/src/components/player/player-header.tsx index 718d2d1..a396252 100644 --- a/projects/website/src/components/player/player-header.tsx +++ b/projects/website/src/components/player/player-header.tsx @@ -13,7 +13,7 @@ import PlayerSteamProfile from "@/components/player/player-steam-profile"; import { getScoreSaberRole } from "@ssr/common/utils/scoresaber.util"; import { DailyChange } from "@/components/statistic/daily-change"; import { ChangeOverTime } from "@/components/statistic/change-over-time"; -import { PlayerStat } from "@ssr/common/player/player-stat"; +import { PlayerStatChange } from "@ssr/common/player/player-stat-change"; const playerData = [ { @@ -27,14 +27,14 @@ const playerData = [ return (
- +

#{formatNumberWithCommas(player.rank)}

- +
); }, @@ -50,14 +50,14 @@ const playerData = [ return (
- +

#{formatNumberWithCommas(player.countryRank)}

- +
); }, @@ -70,10 +70,10 @@ const playerData = [ return (
- +

{formatPp(player.pp)}pp

- +
); }, diff --git a/projects/website/src/components/player/player-stats.tsx b/projects/website/src/components/player/player-stats.tsx index d83c577..2c953ff 100644 --- a/projects/website/src/components/player/player-stats.tsx +++ b/projects/website/src/components/player/player-stats.tsx @@ -5,8 +5,8 @@ import { formatDate } from "@ssr/common/utils/time-utils"; import { ReactNode } from "react"; import Tooltip from "@/components/tooltip"; import { DailyChange } from "@/components/statistic/daily-change"; -import { PlayerStat } from "@ssr/common/player/player-stat"; import { getScoreSaberRole } from "@ssr/common/utils/scoresaber.util"; +import { PlayerStatChange } from "@ssr/common/player/player-stat-change"; type Stat = { name: string; @@ -22,13 +22,11 @@ const playerStats: Stat[] = [ name: "Ranked Play Count", color: "bg-pp", create: (player: ScoreSaberPlayer) => { - const rankedScoresChange = player.statisticChange?.daily.scores?.totalRankedScores; - return { value: ( <> {formatNumberWithCommas(player.statistics.rankedPlayCount)}{" "} - + ), }; @@ -39,7 +37,12 @@ const playerStats: Stat[] = [ color: "bg-pp", create: (player: ScoreSaberPlayer) => { return { - value: formatNumberWithCommas(player.statistics.totalRankedScore), + value: ( + <> + {formatNumberWithCommas(player.statistics.totalRankedScore)}{" "} + + + ), }; }, }, @@ -48,20 +51,23 @@ const playerStats: Stat[] = [ color: "bg-pp", create: (player: ScoreSaberPlayer) => { return { - value: player.statistics.averageRankedAccuracy.toFixed(2) + "%", + value: ( + <> + {player.statistics.averageRankedAccuracy.toFixed(2) + "%"}{" "} + + + ), }; }, }, { name: "Total Play Count", create: (player: ScoreSaberPlayer) => { - const scoresChange = player.statisticChange?.daily.scores; - return { value: ( <> {formatNumberWithCommas(player.statistics.totalPlayCount)}{" "} - + ), }; @@ -71,7 +77,12 @@ const playerStats: Stat[] = [ name: "Total Score", create: (player: ScoreSaberPlayer) => { return { - value: formatNumberWithCommas(player.statistics.totalScore), + value: ( + <> + {formatNumberWithCommas(player.statistics.totalScore)}{" "} + + + ), }; }, }, @@ -79,7 +90,12 @@ const playerStats: Stat[] = [ name: "Total Replays Watched", create: (player: ScoreSaberPlayer) => { return { - value: formatNumberWithCommas(player.statistics.replaysWatched), + value: ( + <> + {formatNumberWithCommas(player.statistics.replaysWatched)}{" "} + + + ), }; }, }, diff --git a/projects/website/src/components/statistic/change-over-time.tsx b/projects/website/src/components/statistic/change-over-time.tsx index f6976b5..3a0fc0c 100644 --- a/projects/website/src/components/statistic/change-over-time.tsx +++ b/projects/website/src/components/statistic/change-over-time.tsx @@ -2,8 +2,9 @@ import ScoreSaberPlayer from "@ssr/common/player/impl/scoresaber-player"; import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils"; import { capitalizeFirstLetter } from "@/common/string-utils"; import Tooltip from "@/components/tooltip"; -import { PlayerStatValue } from "@ssr/common/player/player-stat"; import { ReactElement } from "react"; +import { ChangeRange } from "@ssr/common/player/player"; +import { PlayerStatValue } from "@ssr/common/player/player-stat-change"; type ChangeOverTimeProps = { /** @@ -23,13 +24,9 @@ type ChangeOverTimeProps = { }; export function ChangeOverTime({ player, type, children }: ChangeOverTimeProps) { - const todayStats = player.statisticChange?.daily; - const weeklyStats = player.statisticChange?.weekly; - const monthlyStats = player.statisticChange?.monthly; - - const todayStat = todayStats?.[type.value!]; - const weeklyStat = weeklyStats?.[type.value!]; - const monthlyStat = monthlyStats?.[type.value!]; + const daily = type.value(player, "daily"); + const weekly = type.value(player, "weekly"); + const monthly = type.value(player, "monthly"); // Format values based on stat type const formatChangeValue = (value: number | undefined): string | number => { @@ -39,13 +36,13 @@ export function ChangeOverTime({ player, type, children }: ChangeOverTimeProps) if (value === undefined) { return "No Data"; } - return type.value === "pp" ? formatPp(value) + "pp" : formatNumberWithCommas(value); + return type.type === "Performance Points" ? formatPp(value) + "pp" : formatNumberWithCommas(value); }; // Renders the change for a given time frame - const renderChange = (value: number | undefined, timeFrame: "daily" | "weekly" | "monthly") => ( + const renderChange = (value: number | undefined, range: ChangeRange) => (

- {capitalizeFirstLetter(timeFrame)} Change:{" "} + {capitalizeFirstLetter(range)} Change:{" "} = 0 ? (value === 0 ? "" : "text-green-500") : "text-red-500"}> {formatChangeValue(value)} @@ -62,9 +59,9 @@ export function ChangeOverTime({ player, type, children }: ChangeOverTimeProps) side="bottom" display={

- {renderChange(todayStat, "daily")} - {renderChange(weeklyStat, "weekly")} - {renderChange(monthlyStat, "monthly")} + {renderChange(daily, "daily")} + {renderChange(weekly, "weekly")} + {renderChange(monthly, "monthly")}
} > diff --git a/projects/website/src/components/statistic/daily-change.tsx b/projects/website/src/components/statistic/daily-change.tsx index 2392a43..e81ad71 100644 --- a/projects/website/src/components/statistic/daily-change.tsx +++ b/projects/website/src/components/statistic/daily-change.tsx @@ -1,7 +1,8 @@ import React from "react"; import Tooltip from "@/components/tooltip"; -import { formatNumberWithCommas } from "@ssr/common/utils/number-utils"; -import { PlayerStatValue } from "@ssr/common/player/player-stat"; +import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils"; +import ScoreSaberPlayer from "@ssr/common/player/impl/scoresaber-player"; +import { PlayerStatValue } from "@ssr/common/player/player-stat-change"; interface DailyChangeProps { /** @@ -10,38 +11,39 @@ interface DailyChangeProps { type: PlayerStatValue; /** - * The value of the change + * The player to get the change for */ - change: number | undefined; + player?: ScoreSaberPlayer | undefined; + + /** + * The change (if not using player) + */ + change?: number; /** * The tooltip to display */ tooltip?: React.ReactElement | string; - - /** - * The formater for the change - * - * @param value - */ - format?: (value: number) => string; } -export function DailyChange({ type, change, tooltip, format }: DailyChangeProps) { - const formatValue = format ?? formatNumberWithCommas; - if (change === 0 || change === undefined) { +export function DailyChange({ type, player, change, tooltip }: DailyChangeProps) { + const formatValue = type.type == "Performance Points" ? formatPp : formatNumberWithCommas; + if (!change && player !== undefined) { + change = type.value?.(player, "daily"); + } + if (change === 0 || (change && change < 0.01) || change === undefined) { return null; } const value = (

0 ? "text-green-400" : "text-red-400"}`}> {change > 0 ? "+" : ""} - {`${formatValue(change)}${type.value == "pp" ? "pp" : ""}`} + {`${formatValue(change)}${type.type == "Performance Points" ? "pp" : ""}`}

); if (!tooltip) { - tooltip = `${type.displayName} change compared to yesterday`; + tooltip = `${type.type} change compared to yesterday`; } return (