diff --git a/client/src/pages/Dashboard.jsx b/client/src/pages/Dashboard.jsx index 6a505f2..e287da2 100644 --- a/client/src/pages/Dashboard.jsx +++ b/client/src/pages/Dashboard.jsx @@ -1,43 +1,108 @@ -import React, { useMemo } from 'react'; -import { Link } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; -import { motion } from 'framer-motion'; -import { FiActivity, FiArrowRight, FiCheckCircle, FiClock, FiCpu, FiTarget, FiTrendingUp, FiZap } from 'react-icons/fi'; -import { api } from '../lib/api'; -import { USE_MOCK, mockChallenges, mockDashboardSummary } from '../lib/mockData'; -import SkeletonCard from '../components/SkeletonCard'; -import EmptyState from '../components/EmptyState'; -import PageHeader from '../components/PageHeader'; +import React, { useMemo, useState } from "react"; +import { Link } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; +import { motion } from "framer-motion"; +import { + FiActivity, + FiArrowRight, + FiCheckCircle, + FiClock, + FiCpu, + FiTarget, + FiTrendingUp, + FiZap, +} from "react-icons/fi"; +import { api } from "../lib/api"; +import { + USE_MOCK, + mockChallenges, + mockDashboardSummary, +} from "../lib/mockData"; +import SkeletonCard from "../components/SkeletonCard"; +import EmptyState from "../components/EmptyState"; +import PageHeader from "../components/PageHeader"; const MotionBlock = motion.div; +const buildChallengeQuery = ({ + page, + limit, + search, + difficulty, + category, + sortBy, + sortDir, +}) => { + const params = new URLSearchParams(); + params.set("page", page); + params.set("limit", limit); + params.set("sortBy", sortBy); + params.set("sortDir", sortDir); + if (search) params.set("search", search); + if (difficulty) params.set("difficulty", difficulty); + if (category) params.set("category", category); + return params.toString(); +}; + +const difficultyChips = [ + { value: "", label: "All" }, + { value: "Easy", label: "Easy" }, + { value: "Medium", label: "Medium" }, + { value: "Hard", label: "Hard" }, +]; + const Dashboard = () => { + const [filters, setFilters] = useState({ + page: 1, + limit: 4, + search: "", + difficulty: "", + category: "", + sortBy: "createdAt", + sortDir: "desc", + }); + + const handleFilterChange = (key, value) => { + setFilters((prev) => ({ ...prev, [key]: value, page: 1 })); + }; + const challengesQuery = useQuery({ - queryKey: ['dashboard-challenges'], + queryKey: ["dashboard-challenges", filters], queryFn: async () => { if (USE_MOCK) { - return mockChallenges.slice(0, 4); + let filtered = mockChallenges; + if (filters.search) { + filtered = filtered.filter(c => c.title.toLowerCase().includes(filters.search.toLowerCase()) || c.description.toLowerCase().includes(filters.search.toLowerCase())); + } + if (filters.difficulty) { + filtered = filtered.filter(c => c.difficulty === filters.difficulty); + } + if (filters.category) { + filtered = filtered.filter(c => c.category && c.category.toLowerCase().includes(filters.category.toLowerCase())); + } + return filtered.slice(0, filters.limit); } try { - const res = await api.get('/api/challenges?page=1&limit=4&sortBy=createdAt&sortDir=desc'); + const qs = buildChallengeQuery(filters); + const res = await api.get(`/api/challenges?${qs}`); const data = res.data.data || []; - return data.length > 0 ? data : mockChallenges.slice(0, 4); + return data.length > 0 ? data : mockChallenges.slice(0, filters.limit); } catch { - return mockChallenges.slice(0, 4); + return mockChallenges.slice(0, filters.limit); } }, }); const summaryQuery = useQuery({ - queryKey: ['dashboard-summary'], + queryKey: ["dashboard-summary"], queryFn: async () => { if (USE_MOCK) { return mockDashboardSummary; } try { - const res = await api.get('/api/dashboard/summary'); + const res = await api.get("/api/dashboard/summary"); return res.data.data || mockDashboardSummary; } catch { return mockDashboardSummary; @@ -50,33 +115,35 @@ const Dashboard = () => { ? summaryQuery.data.recentActivity : mockDashboardSummary.recentActivity; const solvedRate = summaryQuery.data?.totalChallenges - ? Math.round((summaryQuery.data.solved / summaryQuery.data.totalChallenges) * 100) + ? Math.round( + (summaryQuery.data.solved / summaryQuery.data.totalChallenges) * 100, + ) : 0; const stats = [ { - label: 'Total Challenges', - value: summaryQuery.data?.totalChallenges ?? '-', + label: "Total Challenges", + value: summaryQuery.data?.totalChallenges ?? "-", icon: FiTarget, - valueClass: 'text-primary', + valueClass: "text-primary", }, { - label: 'Solved', - value: summaryQuery.data?.solved ?? '-', + label: "Solved", + value: summaryQuery.data?.solved ?? "-", icon: FiCheckCircle, - valueClass: 'text-green-500', + valueClass: "text-green-500", }, { - label: 'Pending Reviews', - value: summaryQuery.data?.pending ?? '-', + label: "Pending Reviews", + value: summaryQuery.data?.pending ?? "-", icon: FiClock, - valueClass: 'text-yellow-500', + valueClass: "text-yellow-500", }, { - label: 'Solved Rate', + label: "Solved Rate", value: `${solvedRate}%`, icon: FiTrendingUp, - valueClass: 'text-accent', + valueClass: "text-accent", }, ]; @@ -110,14 +177,19 @@ const Dashboard = () => {
- Push your limits with this week's elite challenge. Solve complex optimizations and climb the global - leaderboards. + Push your limits with this week's elite challenge. Solve + complex optimizations and climb the global leaderboards.
{card.value}
- ▲ 2% ++ {card.value} +
+ + ▲ 2% +{challenge.description}
++ {challenge.description} +
- {submission.challengeId?.title || 'Unknown Challenge'} + {submission.challengeId?.title || "Unknown Challenge"}
{new Date(submission.submittedAt).toLocaleDateString()}
++ {new Date(submission.submittedAt).toLocaleDateString()} +
{submission.status} @@ -253,7 +399,9 @@ const Dashboard = () => { )) ) : ( -
No recent submissions yet. Start with an easy challenge.
++ No recent submissions yet. Start with an easy challenge. +
)}