Skip to content

Commit c2711bf

Browse files
committed
feat: Enhance admin functionality with category management and daily challenge fetching
1 parent 43f7c06 commit c2711bf

File tree

11 files changed

+336
-175
lines changed

11 files changed

+336
-175
lines changed

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

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -245,23 +245,8 @@ SkeletonLoader.displayName = "SkeletonLoader";
245245

246246
// Problem Card Component
247247
const ProblemCard = memo(
248-
({ challenge, isToday = false }: { challenge: Challenge; isToday?: boolean }) => {
248+
({ challenge, isToday = false, stats }: { challenge: Challenge; isToday?: boolean; stats: { usersCount: number; challengesCount: number; solvedChallenges: number } }) => {
249249
const [isLoading, setIsLoading] = useState(false);
250-
const [stats, setStats] = useState({ usersCount: 0, challengesCount: 0, solvedChallenges: 0 });
251-
252-
useEffect(() => {
253-
const fetchStats = async () => {
254-
try {
255-
const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/stats`);
256-
const data = await res.json();
257-
setStats(data);
258-
} catch (error) {
259-
console.error("Failed to fetch stats:", error);
260-
}
261-
};
262-
263-
fetchStats();
264-
}, []);
265250

266251
const navigate = useNavigate();
267252
const handleUpdatePOTD = useCallback(() => {
@@ -451,6 +436,8 @@ const AdminChallenges: React.FC = () => {
451436
users: storeUsers,
452437
fetchChallenges,
453438
fetchUsers,
439+
fetchStats: storeFetchStats,
440+
fetchPOTD,
454441
loading: storeLoading
455442
} = useAdminStore();
456443

@@ -461,20 +448,31 @@ const AdminChallenges: React.FC = () => {
461448
const [filterDifficulty, setFilterDifficulty] = useState<string>("all");
462449
const [sortBy, setSortBy] = useState<string>("date");
463450
const [stats, setStats] = useState({ usersCount: 0, challengesCount: 0, solvedChallenges: 0 });
451+
const [todayChallenge, setTodayChallenge] = useState<Challenge | null>(null);
464452

465453
useEffect(() => {
466-
const fetchStats = async () => {
467-
try {
468-
const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/stats`);
469-
const data = await res.json();
470-
setStats(data);
471-
} catch (error) {
472-
console.error("Failed to fetch stats:", error);
454+
const loadStats = async () => {
455+
const statsData = await storeFetchStats();
456+
if (statsData) {
457+
// Parse formatted strings back to numbers for calculations
458+
const parseFormattedNumber = (str: string) => {
459+
if (typeof str === 'number') return str;
460+
if (str.includes('M+')) return parseFloat(str.replace('M+', '')) * 1000000;
461+
if (str.includes('k+')) return parseFloat(str.replace('k+', '')) * 1000;
462+
if (str.includes('+')) return parseInt(str.replace('+', ''));
463+
return parseInt(str) || 0;
464+
};
465+
466+
setStats({
467+
usersCount: parseFormattedNumber(statsData.usersCount),
468+
challengesCount: parseFormattedNumber(statsData.challengesCount),
469+
solvedChallenges: parseFormattedNumber(statsData.solvedChallenges)
470+
});
473471
}
474472
};
475473

476-
fetchStats();
477-
}, []);
474+
loadStats();
475+
}, [storeFetchStats]);
478476

479477
const [statistics] = useState<Statistics>({
480478
totalProblems: 0,
@@ -494,13 +492,44 @@ const AdminChallenges: React.FC = () => {
494492
fetchChallenges(),
495493
fetchUsers()
496494
]);
495+
496+
// Fetch today's POTD
497+
const potdData = await fetchPOTD();
498+
if (potdData && potdData.challenge) {
499+
const challenge = potdData.challenge;
500+
const solvedUsersCount = challenge.solvedUsers?.length || 0;
501+
502+
// Convert to Challenge interface format
503+
const formattedChallenge: Challenge = {
504+
_id: challenge._id,
505+
title: challenge.title,
506+
createdAt: challenge.createdAt,
507+
category: Array.isArray(challenge.category)
508+
? challenge.category
509+
: challenge.category ? [challenge.category] : [],
510+
difficulty: challenge.difficulty as "Easy" | "Medium" | "Hard",
511+
platform: challenge.platform || "Unknown",
512+
description: challenge.description || "",
513+
problemLink: challenge.problemLink || "#",
514+
solvedUsers: challenge.solvedUsers || [],
515+
solvedUsersCount,
516+
totalUsers: storeUsers.length,
517+
solvedPercentage: storeUsers.length > 0
518+
? Math.round((solvedUsersCount / storeUsers.length) * 100)
519+
: 0,
520+
};
521+
522+
setTodayChallenge(formattedChallenge);
523+
}
497524
} catch (error) {
498525
console.error("Failed to fetch data:", error);
526+
} finally {
527+
// Don't set loading to false here - let the challenges processing effect handle it
499528
}
500529
};
501530

502531
loadData();
503-
}, [fetchChallenges, fetchUsers]);
532+
}, [fetchChallenges, fetchUsers, fetchPOTD, storeUsers.length]);
504533

505534
// Get the total number of users
506535
const totalUsersCount = useMemo(() => {
@@ -606,23 +635,6 @@ const AdminChallenges: React.FC = () => {
606635
// });
607636
// }, []);
608637

609-
// Get today's challenge
610-
const todayChallenge = useMemo(() => {
611-
if (processedChallenges.length === 0) return null;
612-
613-
const today = new Date();
614-
today.setHours(0, 0, 0, 0);
615-
616-
// Find challenge created today or the most recent one
617-
const todaysChallenge = processedChallenges.find((challenge) => {
618-
const challengeDate = new Date(challenge.createdAt);
619-
challengeDate.setHours(0, 0, 0, 0);
620-
return challengeDate.getTime() === today.getTime();
621-
});
622-
623-
return todaysChallenge || processedChallenges[0]; // Return today's challenge or the most recent one
624-
}, [processedChallenges]);
625-
626638
// Filter and sort challenges
627639
useEffect(() => {
628640
let filtered = [...processedChallenges];
@@ -710,7 +722,7 @@ const AdminChallenges: React.FC = () => {
710722
<SkeletonLoader />
711723
</div>
712724
) : (
713-
todayChallenge && <ProblemCard challenge={todayChallenge} isToday={true} />
725+
todayChallenge && <ProblemCard challenge={todayChallenge} isToday={true} stats={stats} />
714726
)}
715727

716728
<div className="flex flex-col sm:flex-row gap-4 mb-6">
@@ -797,7 +809,7 @@ const AdminChallenges: React.FC = () => {
797809
exit={{ opacity: 0, y: -20 }}
798810
transition={{ duration: 0.3 }}
799811
>
800-
<ProblemCard challenge={challenge} />
812+
<ProblemCard challenge={challenge} stats={stats} />
801813
</motion.div>
802814
))
803815
)}

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

Lines changed: 31 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ interface TodayChallenge {
5858
}
5959

6060
export default function Dashboard() {
61-
const { users, challenges, fetchUsers, fetchChallenges } = useAdminStore();
61+
const { users, challenges, fetchUsers, fetchChallenges, fetchStats, fetchPOTD } = useAdminStore();
6262
const [stats, setStats] = useState({ usersCount: 0, challengesCount: 0,collegesCount:0 ,affiliatesCount:0 });
6363
const [collegeData, setCollegeData] = useState<CollegeData[]>([]);
6464
const [problemDifficultyData, setProblemDifficultyData] = useState<ProblemDifficultyData[]>([]);
@@ -78,24 +78,36 @@ export default function Dashboard() {
7878

7979
// Fetch data when component mounts
8080
useEffect(() => {
81-
fetchUsers();
82-
fetchChallenges();
83-
}, [fetchUsers, fetchChallenges]);
84-
85-
useEffect(() => {
86-
const fetchStats = async () => {
87-
try {
88-
const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/stats`);
89-
const data = await res.json();
90-
setStats(data);
91-
setProblemDifficultyData(data.difficultyDistribution);
92-
} catch (error) {
93-
console.error("Failed to fetch stats:", error);
94-
}
95-
};
96-
97-
fetchStats();
98-
}, []);
81+
const loadData = async () => {
82+
await fetchUsers();
83+
await fetchChallenges();
84+
const statsData = await fetchStats();
85+
if (statsData) {
86+
setStats(statsData);
87+
setProblemDifficultyData(statsData.difficultyDistribution || []);
88+
}
89+
90+
// Fetch today's POTD
91+
const potdData = await fetchPOTD();
92+
if (potdData && potdData.challenge) {
93+
const challenge = potdData.challenge;
94+
const solvedCount = challenge.solvedUsers?.length || 0;
95+
96+
setTodayChallenge({
97+
title: challenge.title || "No title available",
98+
solved: solvedCount,
99+
attempted: solvedCount > 0 ? solvedCount + Math.round(solvedCount * 0.3) : 0,
100+
category: Array.isArray(challenge.category)
101+
? challenge.category.join(' ')
102+
: challenge.category || "Not specified",
103+
difficulty: challenge.difficulty || "Medium",
104+
description: challenge.description || "No description available for this problem."
105+
});
106+
}
107+
};
108+
109+
loadData();
110+
}, [fetchUsers, fetchChallenges, fetchStats, fetchPOTD]);
99111

100112
// Calculate weekly user registration data from actual database
101113
useEffect(() => {
@@ -170,29 +182,6 @@ export default function Dashboard() {
170182
}
171183
}, [users]);
172184

173-
// Calculate challenge difficulty distribution from actual database
174-
useEffect(() => {
175-
if (challenges.length > 0) {
176-
177-
// Set today's challenge data
178-
const latestChallenge = challenges[0];
179-
if (latestChallenge) {
180-
const solvedCount = latestChallenge.solvedUsers?.length || 0;
181-
182-
setTodayChallenge({
183-
title: latestChallenge.title || "No title available",
184-
solved: solvedCount,
185-
attempted: solvedCount > 0 ? solvedCount + Math.round(solvedCount * 0.3) : 0,
186-
category: Array.isArray(latestChallenge.category)
187-
? latestChallenge.category.join(' ')
188-
: latestChallenge.category || "Not specified",
189-
difficulty: latestChallenge.difficulty || "Medium",
190-
description: latestChallenge.description || "No description available for this problem."
191-
});
192-
}
193-
}
194-
}, [challenges]);
195-
196185
// Calculate number of unique colleges
197186
// const uniqueCollegesCount = collegeData.length;
198187

0 commit comments

Comments
 (0)