11"use client" ;
22
33import { AnimatePresence , motion } from "framer-motion" ;
4+ import { Toast } from "react-hot-toast" ;
45import {
56 Calendar ,
67 ChevronDown ,
@@ -21,6 +22,7 @@ import React, {
2122} from "react" ;
2223import { useNavigate } from 'react-router-dom' ;
2324import { useAdminStore } from "@/context/AdminContext" ;
25+ import { solvedChallenges } from "@/lib/potdchallenge" ;
2426
2527// Interfaces
2628interface 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