scoresaber-reloaded-v2/src/components/player/Score.tsx

125 lines
4.0 KiB
TypeScript
Raw Normal View History

2023-10-20 20:55:30 +00:00
import { ScoresaberLeaderboardInfo } from "@/schemas/scoresaber/leaderboard";
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
2023-10-20 20:55:30 +00:00
import { ScoresaberScore } from "@/schemas/scoresaber/score";
import { formatNumber } from "@/utils/number";
import { formatDate, formatTimeAgo } from "@/utils/timeUtils";
import {
CheckIcon,
GlobeAsiaAustraliaIcon,
XMarkIcon,
} from "@heroicons/react/20/solid";
2023-10-21 01:38:18 +00:00
import clsx from "clsx";
2023-10-20 20:55:30 +00:00
import Image from "next/image";
import ScoreStatLabel from "./ScoreStatLabel";
type ScoreProps = {
score: ScoresaberScore;
player: ScoresaberPlayer;
2023-10-20 20:55:30 +00:00
leaderboard: ScoresaberLeaderboardInfo;
};
export default function Score({ score, player, leaderboard }: ScoreProps) {
2023-10-21 01:38:18 +00:00
const isFullCombo = score.missedNotes + score.badCuts === 0;
2023-10-20 20:55:30 +00:00
return (
2023-10-23 10:06:03 +00:00
<div className="grid grid-cols-1 pb-2 pt-2 first:pt-0 last:pb-0 md:grid-cols-[1.2fr_6fr_3fr] xl:grid-cols-[1fr_6fr_3fr]">
2023-10-20 20:55:30 +00:00
<div className="ml-3 flex flex-col items-start justify-center">
<div className="hidden w-fit flex-row items-center justify-start gap-1 md:flex">
<GlobeAsiaAustraliaIcon width={20} height={20} />
2023-10-23 10:39:48 +00:00
<p>#{formatNumber(score.rank)}</p>
2023-10-20 20:55:30 +00:00
</div>
<p
className="hidden text-sm text-gray-200 md:block"
title={formatDate(score.timeSet)}
>
{formatTimeAgo(score.timeSet)}
2023-10-20 20:55:30 +00:00
</p>
</div>
{/* Song Image */}
2023-10-20 20:55:30 +00:00
<div className="flex w-full items-center gap-2">
<Image
src={leaderboard.coverImage}
alt={leaderboard.songName}
className="h-fit rounded-md"
width={60}
height={60}
2023-10-24 14:06:47 +00:00
loading="lazy"
placeholder="blur"
2023-10-20 20:55:30 +00:00
/>
{/* Song Info */}
2023-10-20 20:55:30 +00:00
<div className="w-fit truncate text-blue-500">
<p>{leaderboard.songName}</p>
2023-10-23 10:43:17 +00:00
<p className="text-blue-300">
2023-10-20 20:55:30 +00:00
{leaderboard.songAuthorName}{" "}
<span className="text-gray-200">{leaderboard.levelAuthorName}</span>
</p>
</div>
</div>
<div className="flex items-center justify-between p-1 md:items-start md:justify-end">
2023-10-21 01:02:34 +00:00
<div className="flex flex-col md:hidden">
{/* Score rank */}
2023-10-21 01:02:34 +00:00
<div className="flex items-center gap-1">
<GlobeAsiaAustraliaIcon width={20} height={20} />
2023-10-23 10:39:48 +00:00
<p>#{formatNumber(score.rank)}</p>
2023-10-21 01:02:34 +00:00
</div>
{/* Time Set (Mobile) */}
2023-10-21 01:02:34 +00:00
<div>
<p
className="block text-sm text-gray-200 md:hidden"
title={formatDate(score.timeSet)}
>
{formatTimeAgo(score.timeSet)}
2023-10-21 01:02:34 +00:00
</p>
</div>
2023-10-20 20:55:30 +00:00
</div>
{/* PP */}
2023-10-21 01:38:18 +00:00
<div className="flex flex-col justify-end gap-2">
<div className="flex justify-end gap-2">
{score.pp > 0 && (
<ScoreStatLabel
className="bg-blue-500 text-center"
value={formatNumber(score.pp.toFixed(2)) + "pp"}
/>
)}
{/* Percentage score */}
2023-10-20 20:55:30 +00:00
<ScoreStatLabel
2023-10-21 01:38:18 +00:00
value={
!leaderboard.maxScore
? formatNumber(score.baseScore)
: ((score.baseScore / leaderboard.maxScore) * 100).toFixed(
2,
) + "%"
}
2023-10-20 20:55:30 +00:00
/>
2023-10-21 01:38:18 +00:00
</div>
2023-10-21 01:38:18 +00:00
<div className="flex justify-end gap-2">
{/* Missed Notes */}
<ScoreStatLabel
className={clsx(
"min-w-[2rem]",
isFullCombo ? "bg-green-500" : "bg-red-500",
)}
icon={
isFullCombo ? (
<CheckIcon width={20} height={20} />
) : (
<XMarkIcon width={20} height={20} />
)
}
2023-10-21 01:38:18 +00:00
value={
isFullCombo
? "FC"
: formatNumber(score.missedNotes + score.badCuts) + "x"
}
/>
</div>
2023-10-20 20:55:30 +00:00
</div>
</div>
</div>
);
}