Skip to content

Commit

Permalink
calculate users in the frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
joshbtay committed Sep 2, 2024
1 parent de1a180 commit 86a58c6
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 133 deletions.
3 changes: 1 addition & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,11 @@ const config = {
authDomain: "byu-cpc.firebaseapp.com",
};
firebase.initializeApp(config);
export const BACKEND_URL = "https://byu-cpc-backend-tqxfeezgfa-uw.a.run.app";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 60 * 24 * 7, // 1 week
staleTime: 1000 * 60,
staleTime: 1000,
persister: experimental_createPersister({
buster: "1.0.1",
storage: createIdbStorage(),
Expand Down
47 changes: 35 additions & 12 deletions src/components/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import "./Leaderboard.css";
import { useUsers } from "../hooks/UseUser";
import { LeaderboardRow } from "./LeaderboardRow";
import { useThisWeek } from "../hooks/UseWeek";
import { useSearchParams } from "react-router-dom";
import { useAllStudyProblems, useProblems } from "../hooks/UseProblem";
import { useQueries } from "@tanstack/react-query";
import { getStats } from "../score/score";

function formatCodeforcesId(input: string) {
const match = input.match(/^(\d+)(\D.*)$/);
Expand Down Expand Up @@ -34,15 +38,32 @@ export function Leaderboard() {
solvedProblems.codeforces.set(problem, 1);
}
}
for (const user of users) {
for (const problem of user.kattis_data) {
solvedProblems.kattis.set(problem.id, 2);
const links = thisWeek.links ?? {};
const [params] = useSearchParams();
const leaderboard = params.get("leaderboard") || "all";
const { data: allProblems } = useProblems();
const { data: allStudyProblems } = useAllStudyProblems();
const calculatedUsers = useQueries({
queries: users.map((user) => ({
queryKey: [leaderboard, "score", user.id],
queryFn: async () => {
return allProblems && allStudyProblems
? getStats(user, allProblems, allStudyProblems)
: null;
},
enabled: !!allProblems && !!allStudyProblems,
staleTime: 1000 * 60 * 5,
})),
combine: (results) => results.map((r) => r.data).filter((a) => !!a),
});
for (const user of calculatedUsers) {
for (const problem of user.solvedDuringContest["kattis"]) {
solvedProblems.kattis.set(problem, 2);
}
for (const problem of user.cf_data.problems) {
solvedProblems.codeforces.set(problem.id, 2);
for (const problem of user.solvedDuringContest["codeforces"]) {
solvedProblems.codeforces.set(problem, 2);
}
}
const links = thisWeek.links ?? {};
return (
<div className="Leaderboard flexCol w-full align-center">
{thisWeek.topic && (
Expand All @@ -57,7 +78,9 @@ export function Leaderboard() {
{Object.keys(links)
.sort()
.map((key) => (
<a href={links[key]}>{key}</a>
<a key={key} href={links[key]}>
{key}
</a>
))}
</div>
</div>
Expand Down Expand Up @@ -109,15 +132,15 @@ export function Leaderboard() {
</div>
</div>
)}
{users
.filter((a) => !!a.score || a.id === user?.uid)
{calculatedUsers
.filter((a) => !!a.score || a.user.id === user?.uid)
.sort((a, b) => b.score - a.score)
.map((u, i) => (
<LeaderboardRow
key={u.id}
user={u}
key={u.user.id}
userStats={u}
rank={i + 1}
isMe={u.id === user?.uid}
isMe={u.user?.id === user?.uid}
/>
))}
</div>
Expand Down
127 changes: 48 additions & 79 deletions src/components/LeaderboardRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import Flame from "../icons/Flame";
import FlameBorder from "../icons/FlameBorder";
import DeadFlame from "../icons/DeadFlame";
import React from "react";
import { User } from "../hooks/UseUser";
import { useThisWeek } from "../hooks/UseWeek";
import { UserStats } from "../score/score";
function WeeklyProblemBox({
solved,
allProblemsLength,
Expand All @@ -23,24 +23,25 @@ function WeeklyProblemBox({
</div>
);
}
function numberWithCommas(x: number) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

type LeaderboardRowProps = {
user: User;
userStats: UserStats;
rank: number;
isMe: boolean;
};

export function LeaderboardRow({ user, rank, isMe }: LeaderboardRowProps) {
export function LeaderboardRow({ userStats, rank, isMe }: LeaderboardRowProps) {
const userId = userStats.user.id;
const thisWeek = useThisWeek();
const allProblemsLength = thisWeek.kattis.length + thisWeek.codeforces.length;
const solvedKattis = new Set(Object.keys(user.kattis_submissions));
const solvedCodeforces = new Set(Object.keys(user.codeforces_submissions));
const validSolvedKattis = new Set(
user.kattis_data.map((problem) => problem.id)
);
const validSolvedCodeforces = new Set(
user.cf_data.problems.map((problem) => problem.id)
const solvedKattis = new Set(Object.keys(userStats.user.kattis_submissions));
const solvedCodeforces = new Set(
Object.keys(userStats.user.codeforces_submissions)
);

return (
<div className="bg-secondary responsive-fg">
<div className={"flexCol rounded " + (isMe ? "highlight" : "")}>
Expand All @@ -50,7 +51,7 @@ export function LeaderboardRow({ user, rank, isMe }: LeaderboardRowProps) {
<WeeklyProblemBox
key={problemId}
solved={
validSolvedKattis.has(problemId)
userStats.solvedDuringContest["kattis"].has(problemId)
? 2
: solvedKattis.has(problemId)
? 1
Expand All @@ -64,7 +65,7 @@ export function LeaderboardRow({ user, rank, isMe }: LeaderboardRowProps) {
<WeeklyProblemBox
key={problemId}
solved={
validSolvedCodeforces.has(problemId)
userStats.solvedDuringContest["codeforces"].has(problemId)
? 2
: solvedCodeforces.has(problemId)
? 1
Expand All @@ -78,25 +79,26 @@ export function LeaderboardRow({ user, rank, isMe }: LeaderboardRowProps) {
<div className="section flexCol">
<div className="flexRow">
<div className={isMe ? "highlight-text" : ""}>
{rank}. <span className="bold">{user.display_name} </span>
{rank}.{" "}
<span className="bold">{userStats.user.display_name} </span>
</div>
<div
className="streak relative flex-center "
data-tooltip-id={user.id + "-streak"}
data-tooltip-id={userId + "-streak"}
>
{user.cur_streak > 0 && (
{userStats.streak.currentStreak > 0 && (
<div
className={
"z1 relative streakText bold pop-shadow " +
(user.is_active ? "fg-white" : "")
(userStats.streak.isActive ? "fg-white" : "")
}
>
{user.cur_streak}
{userStats.streak.currentStreak}
</div>
)}
{user.is_active ? (
{userStats.streak.isActive ? (
<Flame className="bgImage full flame" />
) : user.cur_streak ? (
) : userStats.streak.currentStreak ? (
<FlameBorder className="bgImage full flame" />
) : (
<DeadFlame className="bgImage full flame" />
Expand All @@ -106,111 +108,78 @@ export function LeaderboardRow({ user, rank, isMe }: LeaderboardRowProps) {
<div className="flexRow gap-12">
<div>
<span className="small">Lv.</span>{" "}
<span className="bold">{user.level}</span>
<span className="bold">{userStats.level.level}</span>
</div>
<div>
<span className="small">Score:</span>{" "}
<span className="">{user.score}</span>
<span className="">{numberWithCommas(userStats.score)}</span>
</div>
</div>
</div>
<div className="flexRow gap-12 wrap section">
<div>{user.days.length} days</div>
<div>{user.cf_data.contests.length} contests</div>
<div>
{user.cf_data.problems.length + user.kattis_data.length} problems
</div>
<div>{userStats.exp.size} days</div>
<div>{userStats.contests.size} contests</div>
<div>{userStats.problemCount} problems</div>
</div>
<div className="flexRow gap-12 section">
<div>Avg. Diff.</div>
{!!user.kattis_data.length && (
{!!userStats.avgDifficulty["kattis"] && (
<div
className="flexRow gap-12"
data-tooltip-id={user.id + "-kattis"}
data-tooltip-id={userId + "-kattis"}
>
<div>Kattis</div>
<div className="bold">
{Math.round(
(user.kattis_data.reduce((a, b) => a + b.difficulty, 0) /
user.kattis_data.length) *
10
) / 10}
</div>
<div className="bold">{userStats.avgDifficulty["kattis"]}</div>
</div>
)}
{!!user.cf_data.problems.length && (
{!!userStats.avgDifficulty["codeforces"] && (
<div
className="flexRow gap-12"
data-tooltip-id={user.id + "-codeforces"}
data-tooltip-id={userId + "-codeforces"}
>
<div>Codeforces</div>
<div className="bold">
{Math.round(
user.cf_data.problems.reduce(
(a, b) => a + b.difficulty,
0
) / user.cf_data.problems.length
)}
{userStats.avgDifficulty["codeforces"]}
</div>
</div>
)}
</div>
</div>
<div className="expBar w-full" data-tooltip-id={user.id + "-exp"}>
<div className="expBar w-full" data-tooltip-id={userId + "-exp"}>
<div
className="expBarFill h-full"
style={{
width: `${
(user.cur_exp / (user.cur_exp + user.next_level)) * 100
(userStats.level.currentExp /
(userStats.level.currentExp + userStats.level.nextLevel)) *
100
}%`,
}}
/>
</div>
<Tooltip id={user.id + "-streak"}>
<Tooltip id={userId + "-streak"}>
<div>
<div>Current Streak: {user.cur_streak}</div>
<div>Best Streak: {user.max_streak}</div>
<div>Current Streak: {userStats.streak.currentStreak}</div>
<div>Best Streak: {userStats.streak.maximumStreak}</div>
</div>
</Tooltip>
<Tooltip id={user.id + "-exp"}>
{user.cur_exp} / {user.cur_exp + user.next_level} XP
<Tooltip id={userId + "-exp"}>
{userStats.level.currentExp} /{" "}
{userStats.level.currentExp + userStats.level.nextLevel} XP
</Tooltip>
<Tooltip id={user.id + "-kattis"}>
<Tooltip id={userId + "-kattis"}>
<div>
<div>
<div>
Avg difficulty:{" "}
{Math.round(
(user.kattis_data.reduce((a, b) => a + b.difficulty, 0) /
user.kattis_data.length) *
10
) / 10}
</div>
<div>
Max difficulty:{" "}
{Math.max(
...user.kattis_data.map((problem) => problem.difficulty)
)}
</div>
<div>Avg difficulty: {userStats.avgDifficulty["kattis"]}</div>
<div>Max difficulty: {userStats.maxDifficulty["kattis"]}</div>
</div>
</div>
</Tooltip>
<Tooltip id={user.id + "-codeforces"}>
<Tooltip id={userId + "-codeforces"}>
<div>
<div>
<div>
Avg difficulty:{" "}
{Math.round(
user.cf_data.problems.reduce((a, b) => a + b.difficulty, 0) /
user.cf_data.problems.length
)}
</div>
<div>
Max difficulty:{" "}
{Math.max(
...user.cf_data.problems.map((problem) => problem.difficulty)
)}
</div>
<div>Avg difficulty: {userStats.avgDifficulty["codeforces"]}</div>
<div>Max difficulty: {userStats.maxDifficulty["codeforces"]}</div>
</div>
</div>
</Tooltip>
Expand Down
31 changes: 31 additions & 0 deletions src/hooks/UseProblem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { BACKEND_URL } from "./base";
import { Platform } from "../types/platform";
type Problem = { [key: string]: { rating: number; name: string } };
export type AllProblems = Record<Platform, Problem>;

export type StudyProblems = Record<Platform, string[]>;

async function getAllProblems(): Promise<AllProblems> {
return (await axios.get(`${BACKEND_URL}/get_all_problems`)).data;
}

export const useProblems = () => {
return useQuery({
queryFn: getAllProblems,
queryKey: ["allProblems"],
staleTime: 1000 * 60 * 60 * 24,
});
};

async function getAllStudyProblems(): Promise<StudyProblems> {
return (await axios.get(`${BACKEND_URL}/get_all_study_problems`)).data;
}

export const useAllStudyProblems = () => {
return useQuery({
queryFn: getAllStudyProblems,
queryKey: ["allStudyProblems"],
});
};
Loading

0 comments on commit 86a58c6

Please sign in to comment.