mirror of
https://github.com/mudabbir-ahmad/peworkshop.git
synced 2026-10-08 03:50:21 +00:00
bug fixes and added stats viewer to admin leaderboard.
This commit is contained in:
1 parent
7075a816c3
commit
d60f52fef3
7 files changed
+1046
-233
No files matched your search
@@ -9,6 +9,10 @@ export default function AdminLeaderboardPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const router = useRouter();
|
||||
// Add state for expanded team details
|
||||
const [expandedTeam, setExpandedTeam] = useState(null);
|
||||
const [teamDetails, setTeamDetails] = useState({});
|
||||
const [loadingDetails, setLoadingDetails] = useState(false);
|
||||
|
||||
// Check if user is admin
|
||||
useEffect(() => {
|
||||
@@ -100,6 +104,45 @@ export default function AdminLeaderboardPage() {
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
// New function to toggle team details dropdown and fetch data
|
||||
const toggleTeamDetails = async (teamId) => {
|
||||
if (expandedTeam === teamId) {
|
||||
// Close dropdown if already open
|
||||
setExpandedTeam(null);
|
||||
} else {
|
||||
// Open dropdown for this team
|
||||
setExpandedTeam(teamId);
|
||||
|
||||
// Only fetch if we don't already have data for this team
|
||||
if (!teamDetails[teamId]) {
|
||||
setLoadingDetails(true);
|
||||
try {
|
||||
const response = await fetch(`/api/admin/teams/${teamId}/stats`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
},
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch team details");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setTeamDetails((prev) => ({
|
||||
...prev,
|
||||
[teamId]: data,
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error("Error fetching team details:", err);
|
||||
} finally {
|
||||
setLoadingDetails(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (status === "loading" || loading) {
|
||||
return <div className="admin-page loading">Loading...</div>;
|
||||
}
|
||||
@@ -146,40 +189,152 @@ export default function AdminLeaderboardPage() {
|
||||
</thead>
|
||||
<tbody>
|
||||
{teams.map((team, index) => (
|
||||
<tr key={team.id}>
|
||||
<td>{index + 1}</td>
|
||||
<td>
|
||||
{team.name} ({team.code})
|
||||
</td>
|
||||
<td>{team.memberCount}</td>
|
||||
<td>
|
||||
<div className="progress-container">
|
||||
<div
|
||||
className="progress-bar"
|
||||
style={{ width: `${team.progress}%` }}
|
||||
></div>
|
||||
<span className="progress-text">
|
||||
{team.cluesFound} / {team.totalClues} (
|
||||
{team.progress}%)
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{team.lastActivity
|
||||
? new Date(team.lastActivity).toLocaleString()
|
||||
: "Never"}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
className="reset-button"
|
||||
onClick={() =>
|
||||
handleResetTeamProgress(team.id, team.name)
|
||||
}
|
||||
<>
|
||||
<tr key={team.id}>
|
||||
<td>{index + 1}</td>
|
||||
<td>
|
||||
{team.name} ({team.code})
|
||||
</td>
|
||||
<td>{team.memberCount}</td>
|
||||
<td>
|
||||
<div className="progress-container">
|
||||
<div
|
||||
className="progress-bar"
|
||||
style={{ width: `${team.progress}%` }}
|
||||
></div>
|
||||
<span className="progress-text">
|
||||
{team.cluesFound} / {team.totalClues} (
|
||||
{team.progress}%)
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{team.lastActivity
|
||||
? new Date(team.lastActivity).toLocaleString()
|
||||
: "Never"}
|
||||
</td>
|
||||
<td>
|
||||
<div className="action-buttons">
|
||||
<button
|
||||
className="reset-button"
|
||||
onClick={() =>
|
||||
handleResetTeamProgress(team.id, team.name)
|
||||
}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
className="details-button"
|
||||
onClick={() => toggleTeamDetails(team.id)}
|
||||
>
|
||||
{expandedTeam === team.id
|
||||
? "Hide Details"
|
||||
: "View Details"}
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/* Team details dropdown */}
|
||||
{expandedTeam === team.id && (
|
||||
<tr
|
||||
key={`details-${team.id}`}
|
||||
className="team-details-row"
|
||||
>
|
||||
Reset Progress
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<td colSpan={6}>
|
||||
<div className="team-details-container">
|
||||
{loadingDetails && !teamDetails[team.id] ? (
|
||||
<div className="details-loading">
|
||||
Loading team details...
|
||||
</div>
|
||||
) : teamDetails[team.id] ? (
|
||||
<>
|
||||
<h3>Team Stats for {team.name}</h3>
|
||||
|
||||
{/* Found clues section */}
|
||||
<div className="details-section">
|
||||
<h4>
|
||||
Found Clues (
|
||||
{teamDetails[team.id].foundClues.length})
|
||||
</h4>
|
||||
{teamDetails[team.id].foundClues.length >
|
||||
0 ? (
|
||||
<table className="details-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Found At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{teamDetails[team.id].foundClues.map(
|
||||
(clue) => (
|
||||
<tr key={`found-${clue.id}`}>
|
||||
<td>{clue.title}</td>
|
||||
<td>
|
||||
{new Date(
|
||||
clue.found_at
|
||||
).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<p className="no-data-message">
|
||||
No clues found yet
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Skipped clues section */}
|
||||
<div className="details-section">
|
||||
<h4>
|
||||
Skipped Clues (
|
||||
{teamDetails[team.id].skippedClues.length}
|
||||
)
|
||||
</h4>
|
||||
{teamDetails[team.id].skippedClues.length >
|
||||
0 ? (
|
||||
<table className="details-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Skipped At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{teamDetails[
|
||||
team.id
|
||||
].skippedClues.map((clue) => (
|
||||
<tr key={`skipped-${clue.id}`}>
|
||||
<td>{clue.title}</td>
|
||||
<td>
|
||||
{new Date(
|
||||
clue.found_at
|
||||
).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<p className="no-data-message">
|
||||
No clues skipped
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="details-error">
|
||||
Error loading team details
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -401,6 +556,97 @@ export default function AdminLeaderboardPage() {
|
||||
font-size: 0.8rem;
|
||||
text-shadow: 0 0 2px rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.details-button {
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0.4rem 0.6rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.details-button:hover {
|
||||
background-color: #303f9f;
|
||||
}
|
||||
|
||||
.team-details-row {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.team-details-container {
|
||||
padding: 1.5rem;
|
||||
margin: 0.5rem 0;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.team-details-container h3 {
|
||||
color: #3f51b5;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.details-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.details-section h4 {
|
||||
color: #68543c;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.details-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.details-table th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
padding: 0.6rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.details-table td {
|
||||
padding: 0.6rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.details-table tr:nth-child(even) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.details-loading {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.details-error {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: #d32f2f;
|
||||
background-color: #ffebee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.no-data-message {
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user