Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useState, useEffect, useCallback } from 'react'; | |
| const MEDALS = ['π₯', 'π₯', 'π₯']; | |
| export default function Leaderboard({ isOpen, onClose }) { | |
| const [data, setData] = useState(null); | |
| const [loading, setLoading] = useState(false); | |
| const fetchLeaderboard = useCallback(() => { | |
| setLoading(true); | |
| fetch(`/api/leaderboard?t=${Date.now()}`) // cache-bust | |
| .then(res => res.json()) | |
| .then(d => { setData(d); setLoading(false); }) | |
| .catch(() => setLoading(false)); | |
| }, []); | |
| // Fetch on first open | |
| useEffect(() => { | |
| if (isOpen && !data) { | |
| fetchLeaderboard(); | |
| } | |
| }, [isOpen, data, fetchLeaderboard]); | |
| if (!isOpen) return null; | |
| return ( | |
| <> | |
| <div className="panel-backdrop" onClick={onClose} /> | |
| <div className="leaderboard-modal"> | |
| <div className="leaderboard-header"> | |
| <h3>π Annotation Leaderboard</h3> | |
| <div className="leaderboard-header-actions"> | |
| <button | |
| className="btn-refresh" | |
| onClick={fetchLeaderboard} | |
| disabled={loading} | |
| title="Refresh" | |
| > | |
| {loading ? 'β³' : 'π'} | |
| </button> | |
| <button className="panel-close" onClick={onClose}>×</button> | |
| </div> | |
| </div> | |
| <div className="leaderboard-body"> | |
| {loading && !data ? ( | |
| <div className="leaderboard-loading"> | |
| <div className="spinner" /> | |
| <p>Tallying scores...</p> | |
| </div> | |
| ) : !data?.leaderboard?.length ? ( | |
| <p className="leaderboard-empty">No annotations yet. Be the first! π</p> | |
| ) : ( | |
| <table className="leaderboard-table"> | |
| <thead> | |
| <tr> | |
| <th>#</th> | |
| <th>Annotator</th> | |
| <th title="Total verifications">β Verified</th> | |
| <th title="Manually added mentions">βοΈ Added</th> | |
| <th title="Documents worked on">π Docs</th> | |
| <th title="Total score">β Score</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {data.leaderboard.map((entry, i) => ( | |
| <tr key={entry.annotator} className={i < 3 ? `rank-${i + 1}` : ''}> | |
| <td className="rank-cell"> | |
| {i < 3 ? MEDALS[i] : i + 1} | |
| </td> | |
| <td className="annotator-cell"> | |
| {entry.annotator} | |
| </td> | |
| <td> | |
| <span className="stat-verified">{entry.verified}</span> | |
| {entry.incorrect > 0 && ( | |
| <span className="stat-detail"> ({entry.correct}β {entry.incorrect}β)</span> | |
| )} | |
| </td> | |
| <td>{entry.humanAdded}</td> | |
| <td>{entry.docsWorked}</td> | |
| <td className="score-cell">{entry.score}</td> | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| )} | |
| </div> | |
| <div className="leaderboard-footer"> | |
| <p>Score = Verified + Added</p> | |
| </div> | |
| </div> | |
| </> | |
| ); | |
| } | |