Skip to content

Commit 55681e4

Browse files
harsha2143harsha2143
andauthored
updated Admin dashboard (#90)
* updated Admin dashboard,challenges stats * challenges page updated * update POTD,adminchallenges page data retrival * updated POTD * updated POTD --------- Co-authored-by: harsha2143 <harshavardhansaripalli21@gamil.com>
1 parent 4b084b9 commit 55681e4

File tree

7 files changed

+433
-186
lines changed

7 files changed

+433
-186
lines changed

client-test/src/components/Admin/AdminChallenges.tsx

Lines changed: 119 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { AnimatePresence, motion } from "framer-motion";
4+
import { Toast } from "react-hot-toast";
45
import {
56
Calendar,
67
ChevronDown,
@@ -21,6 +22,7 @@ import React, {
2122
} from "react";
2223
import { useNavigate } from 'react-router-dom';
2324
import { useAdminStore } from "@/context/AdminContext";
25+
import { solvedChallenges } from "@/lib/potdchallenge";
2426

2527
// Interfaces
2628
interface Challenge {
@@ -247,29 +249,41 @@ const ProblemCard = memo(
247249
({ challenge, isToday = false }: { challenge: Challenge; isToday?: boolean }) => {
248250
const [isLoading, setIsLoading] = useState(false);
249251
const { fetchChallenges } = useAdminStore();
252+
const [stats, setStats] = useState({ usersCount: 0, challengesCount: 0, solvedChallenges: 0 });
250253

251-
const handleUpdatePOTD = useCallback(async () => {
254+
useEffect(() => {
255+
const fetchStats = async () => {
256+
try {
257+
const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/stats`);
258+
const data = await res.json();
259+
setStats(data);
260+
} catch (error) {
261+
console.error("Failed to fetch stats:", error);
262+
}
263+
};
264+
265+
fetchStats();
266+
}, []);
267+
268+
const navigate = useNavigate();
269+
const handleUpdatePOTD = useCallback(() => {
270+
// Navigate to AddChallenge page and just send a flag
252271
setIsLoading(true);
253-
try {
254-
// In a real implementation, make an API call here
255-
// Simulation for the demo
256-
await new Promise(resolve => setTimeout(resolve, 1000));
257-
await fetchChallenges(); // Refresh challenges after update
258-
} catch (error) {
259-
console.error("Error updating POTD:", error);
260-
} finally {
261-
setIsLoading(false);
262-
}
263-
}, [fetchChallenges]);
272+
setTimeout(50000)
273+
navigate("/codingclubadmin/addchallenge", {
274+
state: { fromAdmin: true },
275+
});
276+
toast.success("Redirected to AddChallenge page to view/update POTD!");
277+
}, [navigate]);
264278

265279
const getDifficultyColor = useCallback((difficulty: string): string => {
266280
switch (difficulty.toLowerCase()) {
267281
case "easy":
268-
return "bg-emerald-500/30 text-emerald-500 border-emerald-500/60 dark:bg-emerald-500/20 dark:border-emerald-500/50";
282+
return "bg-emerald-900/50 text-emerald-500 border-emerald-500/60 dark:bg-emerald-500/20 dark:border-emerald-500/50";
269283
case "medium":
270-
return "bg-amber-500/30 text-amber-500 border-amber-500/60 dark:bg-amber-500/20 dark:border-amber-500/50";
284+
return "bg-amber-900/50 text-amber-500 border-amber-500/60 dark:bg-amber-500/20 dark:border-amber-500/50";
271285
case "hard":
272-
return "bg-rose-500/30 text-rose-500 border-rose-500/60 dark:bg-rose-500/20 dark:border-rose-500/50";
286+
return "bg-rose-900/50 text-rose-500 border-rose-500/60 dark:bg-rose-500/20 dark:border-rose-500/50";
273287
default:
274288
return "bg-gray-500/30 text-gray-500 border-gray-500/60 dark:bg-gray-500/20 dark:border-gray-500/50";
275289
}
@@ -284,12 +298,12 @@ const ProblemCard = memo(
284298

285299
return (
286300
<>
287-
<div className="overflow-hidden">
288-
<div className="h-1 rounded-lg overflow-hidden">
301+
<div className="overflow-hidden px-1">
302+
<div className="h-1 rounded-full overflow-hidden">
289303
<div
290-
style={{ width: `${challenge.solvedPercentage}%` }}
291-
className="h-1 bg-blue-600 dark:bg-blue-500"
292-
aria-label={`${challenge.solvedPercentage}% solved`}
304+
style={{ width: `${challenge.solvedPercentage / stats.usersCount}%` }}
305+
className="h-2 bg-blue-600 dark:bg-blue-500"
306+
aria-label={`${challenge.solvedPercentage / stats.usersCount}% solved`}
293307
/>
294308
</div>
295309
</div>
@@ -343,14 +357,14 @@ const ProblemCard = memo(
343357
challenge.category.map((tag, index) => (
344358
<Badge
345359
key={`${tag}-${index}`}
346-
className="bg-blue-900/30 dark:bg-blue-100/50 text-blue-400 dark:text-blue-600 border-blue-500/30"
360+
className="bg-black text-md dark:bg-blue-100/50 text-white dark:text-blue-600 border-blue-500/30"
347361
>
348362
{tag}
349363
</Badge>
350364
))
351365
) : (
352366
<Badge
353-
className="bg-blue-900/30 dark:bg-blue-100/50 text-blue-400 dark:text-blue-600 border-blue-500/30"
367+
className="bg-blue-900/80 dark:bg-blue-100/50 text-blue-400 dark:text-blue-600 border-blue-500/30"
354368
>
355369
{challenge.category}
356370
</Badge>
@@ -363,19 +377,21 @@ const ProblemCard = memo(
363377
<div className="bg-gray-700 dark:bg-gray-100 px-3 py-1 rounded-full">
364378
<span className="text-sm font-medium text-white dark:text-gray-900">
365379
<AnimatedCounter value={challenge.solvedUsersCount || 0} /> /{" "}
366-
{challenge.totalUsers || 0} solved
380+
{stats.usersCount || 0} solved
367381
</span>
368382
</div>
369-
<span className="text-sm font-medium text-blue-400 dark:text-blue-600 mt-1">
370-
{challenge.solvedPercentage || 0}%
383+
<span className="text-sm font-medium text-blue-400 dark:text-blue-600">
384+
{challenge.solvedUsersCount / stats.usersCount || 0}%
371385
</span>
372386
</div>
373387
</div>
374388

375389
{/* Platform info */}
376390
<div className="flex items-center gap-2 text-sm text-gray-400 dark:text-gray-500">
377-
<Code className="h-4 w-4" />
378-
{challenge.platform}
391+
<span className="flex items-center gap-2 bg-secondary dark:bg-muted px-2 py-1 rounded-full text-secondary-foreground dark:text-muted-foreground">
392+
<Code className="h-5 w-4 text-primary" />
393+
{challenge.platform}
394+
</span>
379395
</div>
380396
</div>
381397
)}
@@ -440,13 +456,29 @@ const AdminChallenges: React.FC = () => {
440456
const [searchQuery, setSearchQuery] = useState<string>("");
441457
const [filterDifficulty, setFilterDifficulty] = useState<string>("all");
442458
const [sortBy, setSortBy] = useState<string>("date");
459+
const [stats, setStats] = useState({ usersCount: 0, challengesCount: 0, solvedChallenges: 0 });
460+
461+
useEffect(() => {
462+
const fetchStats = async () => {
463+
try {
464+
const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/stats`);
465+
const data = await res.json();
466+
setStats(data);
467+
} catch (error) {
468+
console.error("Failed to fetch stats:", error);
469+
}
470+
};
471+
472+
fetchStats();
473+
}, []);
474+
443475
const [statistics, setStatistics] = useState<Statistics>({
444476
totalProblems: 0,
445477
averageSolveRate: 0,
446478
topPerformer: "",
447479
lowestPerformer: "",
448480
});
449-
const navigate = useNavigate();
481+
const navigate = useNavigate();
450482

451483
// Fetch challenge and user data from the store
452484
useEffect(() => {
@@ -503,71 +535,72 @@ const AdminChallenges: React.FC = () => {
503535

504536
setProcessedChallenges(processedData);
505537
setFilteredChallenges(processedData);
506-
calculateStatistics(processedData);
507538
setIsLoading(false);
508539
} else if (!storeLoading) {
509540
// If we're not loading and have no challenges, ensure we're not showing loading state
510541
setIsLoading(false);
511542
}
512543
}, [storeRawChallenges, storeLoading, totalUsersCount]);
513544

514-
// Calculate statistics based on challenge data
515-
const calculateStatistics = useCallback((challengesData: Challenge[]) => {
516-
if (challengesData.length === 0) return;
517-
518-
const totalProblems = challengesData.length;
519-
520-
// Calculate average solve rate
521-
const totalSolveRate = challengesData.reduce((sum, challenge) => {
522-
return sum + (challenge.solvedPercentage || 0);
523-
}, 0);
524-
const averageSolveRate = totalProblems > 0 ? Math.round(totalSolveRate / totalProblems) : 0;
525-
526-
// Find category performance
527-
const categoryPerformance: Record<string, { count: number; totalRate: number }> = {};
528-
529-
challengesData.forEach((challenge) => {
530-
const categories = Array.isArray(challenge.category)
531-
? challenge.category
532-
: [challenge.category];
533545

534-
categories.forEach((cat) => {
535-
if (!cat) return; // Skip empty categories
536-
537-
if (!categoryPerformance[cat]) {
538-
categoryPerformance[cat] = { count: 0, totalRate: 0 };
539-
}
540-
categoryPerformance[cat].count += 1;
541-
categoryPerformance[cat].totalRate += challenge.solvedPercentage || 0;
542-
});
543-
});
544546

545-
let topPerformer = "";
546-
let topPerformanceRate = 0;
547-
let lowestPerformer = "";
548-
let lowestPerformanceRate = 100;
549-
550-
Object.entries(categoryPerformance).forEach(([category, data]) => {
551-
if (data.count === 0) return; // Skip if no challenges in this category
552-
553-
const avgRate = data.totalRate / data.count;
554-
if (avgRate > topPerformanceRate) {
555-
topPerformanceRate = avgRate;
556-
topPerformer = category;
557-
}
558-
if (avgRate < lowestPerformanceRate && data.count > 1) {
559-
lowestPerformanceRate = avgRate;
560-
lowestPerformer = category;
561-
}
562-
});
563-
564-
setStatistics({
565-
totalProblems,
566-
averageSolveRate,
567-
topPerformer,
568-
lowestPerformer,
569-
});
570-
}, []);
547+
// // Calculate statistics based on challenge data
548+
// const calculateStatistics = useCallback((challengesData: Challenge[]) => {
549+
// if (challengesData.length === 0) return;
550+
551+
// const totalProblems = challengesData.length;
552+
553+
// // Calculate average solve rate
554+
// const totalSolveRate = challengesData.reduce((sum, challenge) => {
555+
// return sum + (challenge.solvedPercentage || 0);
556+
// }, 0);
557+
// const averageSolveRate = totalProblems > 0 ? Math.round(totalSolveRate / totalProblems) : 0;
558+
559+
// // Find category performance
560+
// const categoryPerformance: Record<string, { count: number; totalRate: number }> = {};
561+
562+
// challengesData.forEach((challenge) => {
563+
// const categories = Array.isArray(challenge.category)
564+
// ? challenge.category
565+
// : [challenge.category];
566+
567+
// categories.forEach((cat) => {
568+
// if (!cat) return; // Skip empty categories
569+
570+
// if (!categoryPerformance[cat]) {
571+
// categoryPerformance[cat] = { count: 0, totalRate: 0 };
572+
// }
573+
// categoryPerformance[cat].count += 1;
574+
// categoryPerformance[cat].totalRate += challenge.solvedPercentage || 0;
575+
// });
576+
// });
577+
578+
// let topPerformer = "";
579+
// let topPerformanceRate = 0;
580+
// let lowestPerformer = "";
581+
// let lowestPerformanceRate = 100;
582+
583+
// Object.entries(categoryPerformance).forEach(([category, data]) => {
584+
// if (data.count === 0) return; // Skip if no challenges in this category
585+
586+
// const avgRate = data.totalRate / data.count;
587+
// if (avgRate > topPerformanceRate) {
588+
// topPerformanceRate = avgRate;
589+
// topPerformer = category;
590+
// }
591+
// if (avgRate < lowestPerformanceRate && data.count > 1) {
592+
// lowestPerformanceRate = avgRate;
593+
// lowestPerformer = category;
594+
// }
595+
// });
596+
597+
// setStatistics({
598+
// totalProblems,
599+
// averageSolveRate,
600+
// topPerformer,
601+
// lowestPerformer,
602+
// });
603+
// }, []);
571604

572605
// Get today's challenge
573606
const todayChallenge = useMemo(() => {
@@ -648,12 +681,12 @@ const AdminChallenges: React.FC = () => {
648681
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
649682
<StatCard
650683
title="Total Problems"
651-
value={statistics.totalProblems}
684+
value={stats.challengesCount}
652685
icon={<Calendar className="h-6 w-6 text-blue-500" />}
653686
/>
654687
<StatCard
655688
title="Average Solve Rate"
656-
value={statistics.averageSolveRate}
689+
value={stats.solvedChallenges / stats.challengesCount}
657690
icon={<SlidersHorizontal className="h-6 w-6 text-blue-500" />}
658691
/>
659692
<StatCard
@@ -731,9 +764,6 @@ const AdminChallenges: React.FC = () => {
731764
<h2 className="text-xl font-semibold text-white dark:text-gray-900 mb-4 flex items-center">
732765
<Calendar className="mr-2 h-5 w-5" />
733766
Previous Problems
734-
<Badge className="ml-3 bg-blue-600 dark:bg-blue-500 text-white">
735-
{filteredChallenges.length}
736-
</Badge>
737767
</h2>
738768

739769
<AnimatePresence>

0 commit comments

Comments
 (0)