From b73da1d7f27c5f97b23cb5c37680e37c17279192 Mon Sep 17 00:00:00 2001 From: aditya singh rathore Date: Fri, 26 Sep 2025 11:00:58 +0530 Subject: [PATCH 1/2] fixed the filters --- .../dashboard/LeaderBoard/PRListModal.tsx | 43 ++++- .../dashboard/LeaderBoard/leaderboard.tsx | 111 ++++------- src/lib/statsProvider.tsx | 175 +++++++++++++----- 3 files changed, 199 insertions(+), 130 deletions(-) diff --git a/src/components/dashboard/LeaderBoard/PRListModal.tsx b/src/components/dashboard/LeaderBoard/PRListModal.tsx index ace9e91c..04e1ca10 100644 --- a/src/components/dashboard/LeaderBoard/PRListModal.tsx +++ b/src/components/dashboard/LeaderBoard/PRListModal.tsx @@ -3,6 +3,7 @@ import React from "react"; import { motion, AnimatePresence } from "framer-motion"; import { FaTimes, FaExternalLinkAlt, FaGithub } from "react-icons/fa"; import { useColorMode } from "@docusaurus/theme-common"; +import { useCommunityStatsContext } from "../../../lib/statsProvider"; interface PRDetails { title: string; @@ -30,9 +31,15 @@ interface PRListModalProps { export default function PRListModal({ contributor, isOpen, onClose }: PRListModalProps): JSX.Element | null { const { colorMode } = useColorMode(); const isDark = colorMode === "dark"; + + // Get filtered PRs from context + const { getFilteredPRsForContributor, currentTimeFilter } = useCommunityStatsContext(); if (!contributor) return null; + // Get filtered PRs instead of using contributor.prDetails + const filteredPRs = getFilteredPRsForContributor(contributor.username); + const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString('en-US', { @@ -54,6 +61,17 @@ export default function PRListModal({ contributor, isOpen, onClose }: PRListModa } }; + // Helper function to get filter display text + const getFilterDisplayText = (filter: string) => { + switch (filter) { + case 'week': return 'This Week'; + case 'month': return 'This Month'; + case 'year': return 'This Year'; + case 'all': return 'All Time'; + default: return 'All Time'; + } + }; + return ( {isOpen && ( @@ -89,7 +107,13 @@ export default function PRListModal({ contributor, isOpen, onClose }: PRListModa {contributor.username}'s Pull Requests

- {contributor.prs} merged PR{contributor.prs !== 1 ? 's' : ''} • {contributor.points} points + {/*Show filtered count and add filter info */} + {filteredPRs.length} merged PR{filteredPRs.length !== 1 ? 's' : ''} • {filteredPRs.length * 10} points + {currentTimeFilter !== 'all' && ( + + ({getFilterDisplayText(currentTimeFilter)}) + + )}

@@ -104,9 +128,10 @@ export default function PRListModal({ contributor, isOpen, onClose }: PRListModa {/* Modal Body */}
- {contributor.prDetails && contributor.prDetails.length > 0 ? ( + {/*Use filteredPRs instead of contributor.prDetails */} + {filteredPRs && filteredPRs.length > 0 ? (
- {contributor.prDetails.map((pr, index) => ( + {filteredPRs.map((pr, index) => ( -

No pull request details available

+

+ {currentTimeFilter === 'all' + ? 'No pull request details available' + : `No PRs found for ${getFilterDisplayText(currentTimeFilter).toLowerCase()}` + } +

- PR details might not be loaded yet or this contributor has no merged PRs. + {currentTimeFilter === 'all' + ? 'PR details might not be loaded yet or this contributor has no merged PRs.' + : `Try selecting a different time period or check "All Time" to see all PRs.` + }

)} diff --git a/src/components/dashboard/LeaderBoard/leaderboard.tsx b/src/components/dashboard/LeaderBoard/leaderboard.tsx index a2770b6c..c63faff8 100644 --- a/src/components/dashboard/LeaderBoard/leaderboard.tsx +++ b/src/components/dashboard/LeaderBoard/leaderboard.tsx @@ -1,4 +1,4 @@ -// src/pages/dashboard/LeaderBoard/leaderboard.tsx +// src/components/dashboard/LeaderBoard/leaderboard.tsx import React, { JSX, useState } from "react"; import { motion } from "framer-motion"; import { @@ -129,11 +129,17 @@ function TopPerformerCard({ ); } -// Define the time period type -type TimePeriod = "all" | "weekly" | "monthly" | "yearly"; - export default function LeaderBoard(): JSX.Element { - const { contributors, stats, loading, error } = useCommunityStatsContext(); + // Get time filter functions from context + const { + contributors, + stats, + loading, + error, + currentTimeFilter, + setTimeFilter + } = useCommunityStatsContext(); + const { colorMode } = useColorMode(); const isDark = colorMode === "dark"; @@ -141,6 +147,7 @@ export default function LeaderBoard(): JSX.Element { const [currentPage, setCurrentPage] = useState(1); const [selectedContributor, setSelectedContributor] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); + const [isSelectChanged, setIsSelectChanged] = useState(false); const itemsPerPage = 10; // Modal handlers @@ -162,61 +169,9 @@ export default function LeaderBoard(): JSX.Element { : []) : contributors; - - const [timePeriod, setTimePeriod] = useState("all"); - const [isSelectChanged, setIsSelectChanged] = useState(false); - - // Get contributions within the selected time period - const getContributionsWithinTimePeriod = (contributors: Contributor[]) => { - if (timePeriod === "all") return contributors; - - // Get date threshold based on selected time period - const now = new Date(); - let threshold = new Date(); - - switch (timePeriod) { - case "weekly": - threshold.setDate(now.getDate() - 7); // Past 7 days - break; - case "monthly": - threshold.setMonth(now.getMonth() - 1); // Past month - break; - case "yearly": - threshold.setFullYear(now.getFullYear() - 1); // Past year - break; - } - - // Since we don't have the actual PR dates in the component, - // we'll simulate filtering by reducing the PR counts by a factor - // In a real implementation, you would filter based on actual PR dates - return contributors.map(contributor => { - // Apply a random factor based on time period to simulate date filtering - // This is just for demonstration - in a real app you'd use actual date data - let factor = 1; - switch (timePeriod) { - case "weekly": - factor = 0.1 + Math.random() * 0.1; // Keep 10-20% for weekly - break; - case "monthly": - factor = 0.3 + Math.random() * 0.2; // Keep 30-50% for monthly - break; - case "yearly": - factor = 0.7 + Math.random() * 0.2; // Keep 70-90% for yearly - break; - } - - const filteredPrs = Math.floor(contributor.prs * factor); - return { - ...contributor, - prs: filteredPrs, - points: filteredPrs * 10, // Assuming each PR is worth 10 points - }; - }).filter(contributor => contributor.prs > 0); // Remove contributors with 0 PRs - }; - - // Filter out excluded users, apply time period filter, and then apply search filter - const filteredContributors = getContributionsWithinTimePeriod(contributors) + // Filter out excluded users and apply search filter + const filteredContributors = contributors .filter((contributor) => !EXCLUDED_USERS.some(excludedUser => contributor.username.toLowerCase() === excludedUser.toLowerCase() @@ -224,20 +179,7 @@ export default function LeaderBoard(): JSX.Element { ) .filter((contributor) => contributor.username.toLowerCase().includes(searchQuery.toLowerCase()) - ) - // Re-sort contributors after filtering to ensure proper ranking - .sort((a, b) => { - // First sort by points (descending) - if (b.points !== a.points) { - return b.points - a.points; - } - // If points are equal, sort by PRs (descending) - if (b.prs !== a.prs) { - return b.prs - a.prs; - } - // If both points and PRs are equal, sort alphabetically by username (ascending) - return a.username.localeCompare(b.username); - }); + ); const totalPages = Math.ceil(filteredContributors.length / itemsPerPage); const indexOfLast = currentPage * itemsPerPage; @@ -343,6 +285,17 @@ export default function LeaderBoard(): JSX.Element { return "regular"; }; + // Helper function for time filter display + const getTimeFilterLabel = (filter: string) => { + switch (filter) { + case 'week': return '📊 This Week'; + case 'month': return '📆 This Month'; + case 'year': return '📅 This Year'; + case 'all': return '🏆 All Time'; + default: return '🏆 All Time'; + } + }; + return (
@@ -368,9 +321,10 @@ export default function LeaderBoard(): JSX.Element {
- diff --git a/src/lib/statsProvider.tsx b/src/lib/statsProvider.tsx index 7455e9aa..5008387c 100644 --- a/src/lib/statsProvider.tsx +++ b/src/lib/statsProvider.tsx @@ -1,4 +1,3 @@ - /** @jsxImportSource react */ import React, { createContext, @@ -12,6 +11,9 @@ import React, { import { githubService, type GitHubOrgStats } from "../services/githubService"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +// Time filter types +export type TimeFilter = 'week' | 'month' | 'year' | 'all'; + interface ICommunityStatsContext { githubStarCount: number; githubStarCountText: string; @@ -28,9 +30,15 @@ interface ICommunityStatsContext { lastUpdated: Date | null; refetch: (signal: AbortSignal) => Promise; clearCache: () => void; - // New properties for leaderboard + + // Leaderboard properties contributors: Contributor[]; stats: Stats | null; + + // New time filter properties + currentTimeFilter: TimeFilter; + setTimeFilter: (filter: TimeFilter) => void; + getFilteredPRsForContributor: (username: string) => PRDetails[]; } // Define types for leaderboard data @@ -69,6 +77,13 @@ interface PullRequestItem { number?: number; } +// Enhanced contributor type for internal processing (stores all PRs) +interface FullContributor extends Omit { + allPRDetails: PRDetails[]; // All PRs regardless of filter + points: number; // Filtered points + prs: number; // Filtered PR count +} + export const CommunityStatsContext = createContext(undefined); interface CommunityStatsProviderProps { @@ -77,9 +92,35 @@ interface CommunityStatsProviderProps { const GITHUB_ORG = "recodehive"; const POINTS_PER_PR = 10; -const MAX_CONCURRENT_REQUESTS = 8; // Increased for better performance +const MAX_CONCURRENT_REQUESTS = 8; const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes cache -const MAX_PAGES_PER_REPO = 20; // Limit pages to prevent infinite loops on huge repos +const MAX_PAGES_PER_REPO = 20; + +// Time filter utility functions +const getTimeFilterDate = (filter: TimeFilter): Date | null => { + const now = new Date(); + switch (filter) { + case 'week': + return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + case 'month': + return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + case 'year': + return new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000); + case 'all': + default: + return null; // No filter + } +}; + +const isPRInTimeRange = (mergedAt: string, filter: TimeFilter): boolean => { + if (filter === 'all') return true; + + const filterDate = getTimeFilterDate(filter); + if (!filterDate) return true; + + const prDate = new Date(mergedAt); + return prDate >= filterDate; +}; export function CommunityStatsProvider({ children }: CommunityStatsProviderProps) { const { @@ -96,16 +137,70 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps const [githubDiscussionsCount, setGithubDiscussionsCount] = useState(0); const [lastUpdated, setLastUpdated] = useState(null); - // New state for leaderboard data - const [contributors, setContributors] = useState([]); + // Time filter state + const [currentTimeFilter, setCurrentTimeFilter] = useState('all'); + + // Enhanced state for leaderboard data (stores all contributors with full PR history) + const [allContributors, setAllContributors] = useState([]); const [stats, setStats] = useState(null); - // Cache state + // Cache state (stores raw data without filters) const [cache, setCache] = useState<{ - data: { contributors: Contributor[]; stats: Stats } | null; + data: { contributors: FullContributor[]; rawStats: { totalPRs: number } } | null; timestamp: number; }>({ data: null, timestamp: 0 }); + // Computed filtered contributors based on current time filter + const contributors = useMemo(() => { + if (!allContributors.length) return []; + + const filteredContributors = allContributors + .map(contributor => { + const filteredPRs = contributor.allPRDetails.filter(pr => + isPRInTimeRange(pr.mergedAt, currentTimeFilter) + ); + + return { + username: contributor.username, + avatar: contributor.avatar, + profile: contributor.profile, + points: filteredPRs.length * POINTS_PER_PR, + prs: filteredPRs.length, + prDetails: filteredPRs, // For backward compatibility, though we'll use the new function + }; + }) + .filter(contributor => contributor.prs > 0) // Only show contributors with PRs in the time range + .sort((a, b) => b.points - a.points || b.prs - a.prs); + + return filteredContributors; + }, [allContributors, currentTimeFilter]); + + // Update stats when contributors change + useEffect(() => { + if (contributors.length > 0) { + setStats({ + flooredTotalPRs: contributors.reduce((sum, c) => sum + c.prs, 0), + totalContributors: contributors.length, + flooredTotalPoints: contributors.reduce((sum, c) => sum + c.points, 0), + }); + } + }, [contributors]); + + // Function to get filtered PRs for a specific contributor (for PR view modal) + const getFilteredPRsForContributor = useCallback((username: string): PRDetails[] => { + const contributor = allContributors.find(c => c.username === username); + if (!contributor) return []; + + return contributor.allPRDetails + .filter(pr => isPRInTimeRange(pr.mergedAt, currentTimeFilter)) + .sort((a, b) => new Date(b.mergedAt).getTime() - new Date(a.mergedAt).getTime()); // Sort by newest first + }, [allContributors, currentTimeFilter]); + + // Time filter setter function + const setTimeFilter = useCallback((filter: TimeFilter) => { + setCurrentTimeFilter(filter); + }, []); + const fetchAllOrgRepos = useCallback(async (headers: Record) => { const repos: any[] = []; let page = 1; @@ -126,10 +221,6 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps const fetchMergedPRsForRepo = useCallback(async (repoName: string, headers: Record) => { const mergedPRs: PullRequestItem[] = []; - let page = 1; - - // Create promises for parallel pagination - const pagePromises: Promise[] = []; // First, get the first page to estimate total pages const firstResp = await fetch( @@ -151,10 +242,10 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps // If we got less than 100, that's all there is if (firstPRs.length < 100) return mergedPRs; - // Estimate remaining pages (with a reasonable limit) - const maxPages = Math.min(MAX_PAGES_PER_REPO, 10); // Conservative estimate - // Create parallel requests for remaining pages + const pagePromises: Promise[] = []; + const maxPages = Math.min(MAX_PAGES_PER_REPO, 10); + for (let i = 2; i <= maxPages; i++) { pagePromises.push( fetch( @@ -180,12 +271,12 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps return mergedPRs; }, []); - // Concurrent processing function with controlled concurrency + // Enhanced processing function that stores all PR data without filtering const processBatch = useCallback(async ( repos: any[], headers: Record - ): Promise<{ contributorMap: Map; totalMergedPRs: number }> => { - const contributorMap = new Map(); + ): Promise<{ contributorMap: Map; totalMergedPRs: number }> => { + const contributorMap = new Map(); let totalMergedPRs = 0; // Process repos in batches to control concurrency @@ -218,19 +309,16 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps username, avatar: pr.user.avatar_url, profile: pr.user.html_url, - points: 0, - prs: 0, - prDetails: [], + points: 0, // Will be calculated later based on filter + prs: 0, // Will be calculated later based on filter + allPRDetails: [], // Store all PRs here }); } const contributor = contributorMap.get(username)!; - contributor.prs++; - contributor.points += POINTS_PER_PR; - // Add detailed PR information + // Add detailed PR information to the full list if (pr.title && pr.html_url && pr.merged_at && pr.number) { - contributor.prDetails = contributor.prDetails || []; - contributor.prDetails.push({ + contributor.allPRDetails.push({ title: pr.title, url: pr.html_url, mergedAt: pr.merged_at, @@ -252,9 +340,7 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps // Check cache first const now = Date.now(); if (cache.data && (now - cache.timestamp) < CACHE_DURATION) { - // console.log('Using cached leaderboard data'); - setContributors(cache.data.contributors); - setStats(cache.data.stats); + setAllContributors(cache.data.contributors); setLoading(false); return; } @@ -288,22 +374,16 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps // Process leaderboard data with concurrent processing const { contributorMap, totalMergedPRs } = await processBatch(repos, headers); - const sortedContributors = Array.from(contributorMap.values()).sort( - (a, b) => b.points - a.points || b.prs - a.prs - ); - - const statsData = { - flooredTotalPRs: totalMergedPRs, - totalContributors: sortedContributors.length, - flooredTotalPoints: sortedContributors.reduce((sum, c) => sum + c.points, 0), - }; + const contributorsArray = Array.from(contributorMap.values()); - setContributors(sortedContributors); - setStats(statsData); + setAllContributors(contributorsArray); - // Cache the results + // Cache the results (raw data without filtering) setCache({ - data: { contributors: sortedContributors, stats: statsData }, + data: { + contributors: contributorsArray, + rawStats: { totalPRs: totalMergedPRs } + }, timestamp: now }); @@ -326,9 +406,9 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps const clearCache = useCallback(() => { githubService.clearCache(); - setCache({ data: null, timestamp: 0 }); // Clear local cache too + setCache({ data: null, timestamp: 0 }); const abortController = new AbortController(); - fetchAllStats(abortController.signal);// Refetch data after clearing cache + fetchAllStats(abortController.signal); }, [fetchAllStats]); useEffect(() => { @@ -364,6 +444,9 @@ export function CommunityStatsProvider({ children }: CommunityStatsProviderProps clearCache, contributors, stats, + currentTimeFilter, + setTimeFilter, + getFilteredPRsForContributor, }; return ( @@ -386,13 +469,13 @@ export const convertStatToText = (num: number): string => { typeof Intl === "object" && Intl && typeof Intl.NumberFormat === "function"; if (!hasIntlSupport) { - return `${(num / 1000).toFixed(1)}k`; // Fallback for environments without Intl support + return `${(num / 1000).toFixed(1)}k`; } const formatter = new Intl.NumberFormat("en-US", { notation: "compact", compactDisplay: "short", - maximumSignificantDigits: 3, // More precise formatting + maximumSignificantDigits: 3, }); return formatter.format(num); }; \ No newline at end of file From 950b8ee9eec41bba54335508923f52084d928209 Mon Sep 17 00:00:00 2001 From: aditya singh rathore <142787780+Adez017@users.noreply.github.com> Date: Fri, 26 Sep 2025 13:00:14 +0530 Subject: [PATCH 2/2] Update src/lib/statsProvider.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/lib/statsProvider.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/statsProvider.tsx b/src/lib/statsProvider.tsx index 5008387c..af910a8c 100644 --- a/src/lib/statsProvider.tsx +++ b/src/lib/statsProvider.tsx @@ -102,8 +102,11 @@ const getTimeFilterDate = (filter: TimeFilter): Date | null => { switch (filter) { case 'week': return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); - case 'month': - return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + case 'month': { + const lastMonth = new Date(now); + lastMonth.setMonth(now.getMonth() - 1); + return lastMonth; + } case 'year': return new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000); case 'all':