Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions src/components/dashboard/LeaderBoard/PRListModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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', {
Expand All @@ -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 (
<AnimatePresence>
{isOpen && (
Expand Down Expand Up @@ -89,7 +107,13 @@ export default function PRListModal({ contributor, isOpen, onClose }: PRListModa
{contributor.username}'s Pull Requests
</h2>
<p className={`pr-modal-subtitle ${isDark ? "dark" : "light"}`}>
{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
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The points calculation uses a magic number (10). Consider importing POINTS_PER_PR constant from statsProvider or defining it as a shared constant to maintain consistency.

Copilot uses AI. Check for mistakes.

{currentTimeFilter !== 'all' && (
<span style={{ marginLeft: '8px', opacity: 0.7 }}>
({getFilterDisplayText(currentTimeFilter)})
</span>
)}
</p>
</div>
</div>
Expand All @@ -104,9 +128,10 @@ export default function PRListModal({ contributor, isOpen, onClose }: PRListModa

{/* Modal Body */}
<div className={`pr-modal-body ${isDark ? "dark" : "light"}`}>
{contributor.prDetails && contributor.prDetails.length > 0 ? (
{/*Use filteredPRs instead of contributor.prDetails */}
{filteredPRs && filteredPRs.length > 0 ? (
<div className="pr-list">
{contributor.prDetails.map((pr, index) => (
{filteredPRs.map((pr, index) => (
<motion.div
key={`${pr.repoName}-${pr.number}`}
className={`pr-item ${isDark ? "dark" : "light"}`}
Expand Down Expand Up @@ -148,9 +173,17 @@ export default function PRListModal({ contributor, isOpen, onClose }: PRListModa
) : (
<div className={`pr-empty-state ${isDark ? "dark" : "light"}`}>
<FaGithub className="pr-empty-icon" />
<p>No pull request details available</p>
<p>
{currentTimeFilter === 'all'
? 'No pull request details available'
: `No PRs found for ${getFilterDisplayText(currentTimeFilter).toLowerCase()}`
}
</p>
<p className="pr-empty-subtitle">
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.`
}
</p>
</div>
)}
Expand Down
111 changes: 32 additions & 79 deletions src/components/dashboard/LeaderBoard/leaderboard.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -129,18 +129,25 @@ 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";

const [searchQuery, setSearchQuery] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [selectedContributor, setSelectedContributor] = useState<Contributor | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isSelectChanged, setIsSelectChanged] = useState(false);
const itemsPerPage = 10;

// Modal handlers
Expand All @@ -162,82 +169,17 @@ export default function LeaderBoard(): JSX.Element {
: [])
: contributors;


const [timePeriod, setTimePeriod] = useState<TimePeriod>("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()
)
)
.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;
Expand Down Expand Up @@ -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 (
<div className={`leaderboard-container ${isDark ? "dark" : "light"}`}>
<div className="leaderboard-content">
Expand All @@ -368,24 +321,24 @@ export default function LeaderBoard(): JSX.Element {
<label htmlFor="time-period-filter" className="filter-label">Time Period:</label>
<select
id="time-period-filter"
value={timePeriod}
value={currentTimeFilter}
onChange={(e) => {
setTimePeriod(e.target.value as TimePeriod);
// Use setTimeFilter from context
setTimeFilter(e.target.value as any);
Copy link

Copilot AI Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using as any bypasses TypeScript's type safety. Since TimeFilter is already defined, cast to the specific type: setTimeFilter(e.target.value as TimeFilter) to maintain type safety.

Suggested change
setTimeFilter(e.target.value as any);
setTimeFilter(e.target.value as TimeFilter);

Copilot uses AI. Check for mistakes.

setCurrentPage(1);
setIsSelectChanged(true);
setTimeout(() => setIsSelectChanged(false), 1200);
}}
className={`time-filter-select ${isDark ? "dark" : "light"} ${isSelectChanged ? 'highlight-change' : ''}`}
>
<option value="all">🏆 All Time</option>
<option value="yearly">📅 Past Year</option>
<option value="monthly">📆 Past Month</option>
<option value="weekly">📊 Past Week</option>
<option value="year">📅 This Year</option>
<option value="month">📆 This Month</option>
<option value="week">📊 This Week</option>
</select>
</div>
</div>
<div className="top-performers-grid">

<TopPerformerCard contributor={filteredContributors[1]} rank={2} onPRClick={handlePRClick} />
<TopPerformerCard contributor={filteredContributors[0]} rank={1} onPRClick={handlePRClick} />
<TopPerformerCard contributor={filteredContributors[2]} rank={3} onPRClick={handlePRClick} />
Expand Down
Loading
Loading