diff --git a/app/(event)/event/[id]/page.tsx b/app/(event)/event/[id]/page.tsx new file mode 100644 index 0000000..3f4aed5 --- /dev/null +++ b/app/(event)/event/[id]/page.tsx @@ -0,0 +1,19 @@ +import EventLeaderboard from '@/ui/events/Leaderboard'; +import { cookies } from 'next/headers'; +import { notFound } from 'next/navigation'; + +export const metadata = { + title: 'Tableau des scores - Peer-at Code', + description: 'Suivez la progression des élèves en direct' +}; + +export default async function Page({ params }: { params: { id: number } }) { + const { id } = params; + + if (!id) { + notFound(); + } + + const token = cookies().get('token')?.value; + return ; +} diff --git a/lib/hooks/use-leaderboard.ts b/lib/hooks/use-leaderboard.ts index b2c684f..e4def4a 100644 --- a/lib/hooks/use-leaderboard.ts +++ b/lib/hooks/use-leaderboard.ts @@ -1,6 +1,10 @@ import useSWR from 'swr'; -import { getScores } from '../leaderboard'; +import { getScores, getScoresEvent } from '../leaderboard'; export function useLeaderboard({ token }: { token: string }) { return useSWR('leaderboard', () => getScores({ token })); } + +export function useLeaderboardEvent({ token, id }: { token: string; id: number }) { + return useSWR(`leaderboard/${id}`, () => getScoresEvent({ token, id })); +} diff --git a/lib/leaderboard.ts b/lib/leaderboard.ts index 7f32d3b..d77991c 100644 --- a/lib/leaderboard.ts +++ b/lib/leaderboard.ts @@ -1,5 +1,5 @@ import fetcher from './fetcher'; -import type { Group } from './players'; +import { type Group } from './groups'; export const getScores = async ({ token }: { token: string }): Promise => { const { data, status } = await fetcher.get(`/leaderboard`, { @@ -21,6 +21,32 @@ export const getScores = async ({ token }: { token: string }): Promise return scores as Score[]; }; +export const getScoresEvent = async ({ + token, + id +}: { + token: string; + id: number; +}): Promise => { + const { data, status } = await fetcher.get(`/leaderboard/${id}`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + + const scores = data; + + if (status !== 200) { + throw new Error('Failed to fetch scores'); + } + + if (!scores) { + return {} as ScoreEvent; + } + + return scores as ScoreEvent; +}; + export type Score = { score: number; tries: number; @@ -30,3 +56,22 @@ export type Score = { avatar: string; rank: number; }; + +export type ScoreEvent = { + start_date: string; + end_date: string; + groups: [ + { + name: string; + rank: number; + players: [ + { + pseudo: string; + tries: number; + completions: number; + score: number; + } + ]; + } + ]; +}; diff --git a/ui/events/Leaderboard.tsx b/ui/events/Leaderboard.tsx new file mode 100644 index 0000000..777caf9 --- /dev/null +++ b/ui/events/Leaderboard.tsx @@ -0,0 +1,67 @@ +'use client'; + +import { useLeaderboardEvent } from '@/lib/hooks/use-leaderboard'; +import { cn } from '@/lib/utils'; +// import { Timer } from '../Timer'; +import Podium from './podium/Podium'; +import { useMemo } from 'react'; + +const SCORE_COLORS = ['text-yellow-400', 'text-gray-400', 'text-orange-400']; + +export default function EventLeaderboard({ token, id }: { token: string; id: number }) { + const { data, isLoading } = useLeaderboardEvent({ token, id }); + + const scores = [data?.groups] + .flat() + .sort((a, b) => a!.rank - b!.rank) + .map((group, place) => ({ + ...group, + place + })); + + return ( +
+ {!isLoading && data && } + {/* */} +
+
    + {!isLoading && + data?.groups.map((group, key) => ( +
  • +
    + + {group.rank} + +
    +
    + {group.name} + + {group.players + ?.map((p) => p.pseudo) + .sort((a, b) => a.localeCompare(b)) + .join(', ')} + +
    +
    +
    +
    +
    + Essaies + + {group.players.reduce((a, b) => a + b.tries, 0)} + +
    +
    + Score + + {group.players.reduce((a, b) => a + b.score, 0)} + +
    +
    +
  • + ))} +
+
+
+ ); +} diff --git a/ui/events/podium/Podium.tsx b/ui/events/podium/Podium.tsx new file mode 100644 index 0000000..370358d --- /dev/null +++ b/ui/events/podium/Podium.tsx @@ -0,0 +1,20 @@ +import PodiumStep from './PodiumStep'; + +export default function Podium({ score }: { score: any }) { + const podium = [2, 0, 1] + .reduce((podiumOrder, position) => [...podiumOrder, score[position]] as any, []) + .filter(Boolean); + + console.log(podium); + + return ( +
+ {podium.map((group: any, index: number) => ( + + ))} +
+ ); +} diff --git a/ui/events/podium/PodiumStep.tsx b/ui/events/podium/PodiumStep.tsx new file mode 100644 index 0000000..ae5ab3e --- /dev/null +++ b/ui/events/podium/PodiumStep.tsx @@ -0,0 +1,64 @@ +import { motion } from 'framer-motion'; + +export const positions: Record = { + 0: '1 er', + 1: '2 ème', + 2: '3 ème' +}; + +export default function PodiumStep({ + podium, + group, + index +}: { + podium: any; + group: any; + index: number; +}) { + return ( +
+ ({ + opacity: 1, + transition: { + delay: podium.length - group.place + 1, + duration: 0.75 + } + }), + hidden: { opacity: 0 } + }} + className="w-16 items-center justify-center text-center" + > + {group.name} + + ({ + height: 200 * ((podium.length - group.place) / podium.length), + opacity: 2, + transition: { + delay: podium.length - group.place, + duration: 1.25, + ease: 'backInOut' + } + }), + hidden: { opacity: 0, height: 0 } + }} + className="flex w-16 cursor-pointer place-content-center rounded-t-lg bg-brand shadow-lg hover:bg-opacity-80" + style={{ + marginBottom: -1, + filter: `opacity(${0.1 + (podium.length - group.place) / podium.length})` + }} + > + {positions[group.place]} + +
+ ); +}