This commit is contained in:
parent
6c809696ea
commit
63199cace6
@ -5,6 +5,7 @@ import Card from "@/components/Card";
|
|||||||
import Container from "@/components/Container";
|
import Container from "@/components/Container";
|
||||||
import Label from "@/components/Label";
|
import Label from "@/components/Label";
|
||||||
import Pagination from "@/components/Pagination";
|
import Pagination from "@/components/Pagination";
|
||||||
|
import PlayerChart from "@/components/PlayerChart";
|
||||||
import ScoreStatLabel from "@/components/ScoreStatLabel";
|
import ScoreStatLabel from "@/components/ScoreStatLabel";
|
||||||
import { Spinner } from "@/components/Spinner";
|
import { Spinner } from "@/components/Spinner";
|
||||||
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
|
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
|
||||||
@ -135,7 +136,7 @@ export default function Player({ params }: { params: { id: string } }) {
|
|||||||
<Avatar url={playerData.profilePicture} label="Avatar" />
|
<Avatar url={playerData.profilePicture} label="Avatar" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 flex flex-col items-center gap-2 xs:items-start">
|
<div className="mt-1 flex w-full flex-col items-center gap-2 xs:items-start">
|
||||||
<p className="text-2xl">{playerData.name}</p>
|
<p className="text-2xl">{playerData.name}</p>
|
||||||
|
|
||||||
<div className="flex gap-3 text-xl">
|
<div className="flex gap-3 text-xl">
|
||||||
@ -184,6 +185,8 @@ export default function Player({ params }: { params: { id: string } }) {
|
|||||||
value={formatNumber(playerData.scoreStats.replaysWatched)}
|
value={formatNumber(playerData.scoreStats.replaysWatched)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<PlayerChart scoresaber={player.player} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
121
src/components/PlayerChart.tsx
Normal file
121
src/components/PlayerChart.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
|
||||||
|
import { formatNumber } from "@/utils/number";
|
||||||
|
import {
|
||||||
|
CategoryScale,
|
||||||
|
Chart as ChartJS,
|
||||||
|
Legend,
|
||||||
|
LineElement,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from "chart.js";
|
||||||
|
import { Line } from "react-chartjs-2";
|
||||||
|
|
||||||
|
ChartJS.register(
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
PointElement,
|
||||||
|
LineElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
);
|
||||||
|
|
||||||
|
type PlayerChartProps = {
|
||||||
|
className?: string;
|
||||||
|
scoresaber: ScoresaberPlayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const options: any = {
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
aspectRatio: 1,
|
||||||
|
interaction: {
|
||||||
|
mode: "index",
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
ticks: {
|
||||||
|
autoSkip: true,
|
||||||
|
maxTicksLimit: 4,
|
||||||
|
},
|
||||||
|
reverse: true,
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
ticks: {
|
||||||
|
autoSkip: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
radius: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: "top" as const,
|
||||||
|
labels: {
|
||||||
|
color: "white",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label(context: any) {
|
||||||
|
switch (context.dataset.label) {
|
||||||
|
case "Rank": {
|
||||||
|
return `Rank #${formatNumber(context.parsed.y.toFixed(0))}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PlayerChart({
|
||||||
|
className,
|
||||||
|
scoresaber,
|
||||||
|
}: PlayerChartProps) {
|
||||||
|
const history: number[] = scoresaber.histories
|
||||||
|
.split(",")
|
||||||
|
.map(function (item) {
|
||||||
|
return parseInt(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
let labels = [];
|
||||||
|
for (let i = history.length; i > 0; i--) {
|
||||||
|
let label = `${i} days ago`;
|
||||||
|
if (i === 1) {
|
||||||
|
label = "now";
|
||||||
|
}
|
||||||
|
if (i === 2) {
|
||||||
|
label = "yesterday";
|
||||||
|
}
|
||||||
|
labels.push(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
lineTension: 0.4,
|
||||||
|
data: history,
|
||||||
|
label: "Rank",
|
||||||
|
borderColor: "#3e95cd",
|
||||||
|
fill: false,
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-[280px] w-full">
|
||||||
|
<Line className={className} options={options} data={data} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -22,6 +22,7 @@ export default function SearchPlayer() {
|
|||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
async function searchPlayer(search: string) {
|
async function searchPlayer(search: string) {
|
||||||
|
// Check if the search is a profile link
|
||||||
if (search.startsWith("https://scoresaber.com/u/")) {
|
if (search.startsWith("https://scoresaber.com/u/")) {
|
||||||
const id = search.split("/").pop();
|
const id = search.split("/").pop();
|
||||||
if (id == undefined) return;
|
if (id == undefined) return;
|
||||||
@ -31,6 +32,8 @@ export default function SearchPlayer() {
|
|||||||
|
|
||||||
setPlayers([player]);
|
setPlayers([player]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search by name
|
||||||
const players = await searchByName(search);
|
const players = await searchByName(search);
|
||||||
if (players == undefined) return;
|
if (players == undefined) return;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user