diff --git a/frontend/__tests__/unit/components/Card.test.tsx b/frontend/__tests__/unit/components/Card.test.tsx index 89a2ba8455..087d41a397 100644 --- a/frontend/__tests__/unit/components/Card.test.tsx +++ b/frontend/__tests__/unit/components/Card.test.tsx @@ -76,6 +76,9 @@ jest.mock('react-icons/fa6', () => ({ FaXTwitter: (props: React.SVGProps) => ( ), + FaCodePullRequest: (props: React.SVGProps) => ( + + ), })) jest.mock('react-icons/fa', () => ({ @@ -730,4 +733,14 @@ describe('Card', () => { expect(socialLink).toHaveAttribute('aria-label', 'Social media link') }) }) + it('renders PR count badge when pullRequestCount is provided', () => { + render() + expect(screen.getByText('3 open PRs')).toBeInTheDocument() + expect(screen.getByTestId('pull-request-icon')).toBeInTheDocument() + }) + + it('does not render PR count badge when pullRequestCount is 0', () => { + render() + expect(screen.queryByTestId('pull-request-icon')).not.toBeInTheDocument() + }) }) diff --git a/frontend/__tests__/unit/components/IssuesTable.test.tsx b/frontend/__tests__/unit/components/IssuesTable.test.tsx index 623a044cfe..5fbc7ac3eb 100644 --- a/frontend/__tests__/unit/components/IssuesTable.test.tsx +++ b/frontend/__tests__/unit/components/IssuesTable.test.tsx @@ -525,6 +525,19 @@ describe('', () => { const headers = within(table).getAllByRole('columnheader') expect(headers).toHaveLength(3) }) + it('calculates correct column count with PR count column', () => { + render() + const table = screen.getByRole('table') + const headers = within(table).getAllByRole('columnheader') + expect(headers).toHaveLength(5) + }) + + it('calculates correct column count without assignee but with PR count column', () => { + render() + const table = screen.getByRole('table') + const headers = within(table).getAllByRole('columnheader') + expect(headers).toHaveLength(4) + }) }) describe('Default Props', () => { diff --git a/frontend/src/app/contribute/page.tsx b/frontend/src/app/contribute/page.tsx index 24317b1bfd..f67194c67e 100644 --- a/frontend/src/app/contribute/page.tsx +++ b/frontend/src/app/contribute/page.tsx @@ -51,6 +51,7 @@ const ContributePage = () => { labels={issue.labels} projectLink={issue.projectUrl} projectName={issue.projectName} + pullRequestCount={issue.pullRequests?.length} summary={issue.summary ?? ''} title={issue.title} url={issue.url} diff --git a/frontend/src/components/Card.tsx b/frontend/src/components/Card.tsx index 61f2770e85..d291e9f6b8 100644 --- a/frontend/src/components/Card.tsx +++ b/frontend/src/components/Card.tsx @@ -1,6 +1,6 @@ import { Tooltip } from '@heroui/tooltip' import Link from 'next/link' -import { FaCalendar } from 'react-icons/fa6' +import { FaCalendar, FaCodePullRequest } from 'react-icons/fa6' import { IconWrapper } from 'wrappers/IconWrapper' import type { CardProps } from 'types/card' import { ICONS } from 'utils/data' @@ -20,6 +20,7 @@ const Card = ({ level, projectLink, projectName, + pullRequestCount, social, summary, timeline, @@ -137,6 +138,15 @@ const Card = ({ )} + {/* PR Count Badge */} + {!!pullRequestCount && ( +
+ + + {pullRequestCount} open PR{pullRequestCount === 1 ? '' : 's'} + +
+ )} {/* Contributors section */}
{topContributors?.map((contributor, index) => ( diff --git a/frontend/src/components/IssuesTable.tsx b/frontend/src/components/IssuesTable.tsx index e46c70eed1..f34b5d5ce8 100644 --- a/frontend/src/components/IssuesTable.tsx +++ b/frontend/src/components/IssuesTable.tsx @@ -5,6 +5,7 @@ import Image from 'next/image' import { useRouter } from 'next/navigation' import type React from 'react' +import { FaCodePullRequest } from 'react-icons/fa6' import { LabelList } from 'components/LabelList' export type IssueRow = { @@ -17,12 +18,14 @@ export type IssueRow = { assignees?: Array<{ avatarUrl: string; login: string; name: string }> url?: string deadline?: string | null + pullRequestCount?: number } interface IssuesTableProps { issues: IssueRow[] showAssignee?: boolean showDeadline?: boolean + showPullRequestCount?: boolean onIssueClick?: (issueNumber: number) => void issueUrl?: (issueNumber: number) => string maxVisibleLabels?: number @@ -35,6 +38,7 @@ const IssuesTable: React.FC = ({ issues, showAssignee = true, showDeadline = false, + showPullRequestCount = false, onIssueClick, issueUrl, maxVisibleLabels = MAX_VISIBLE_LABELS, @@ -73,6 +77,7 @@ const IssuesTable: React.FC = ({ let count = 3 // Title, Status, Labels if (showAssignee) count++ if (showDeadline) count++ + if (showPullRequestCount) count++ return count } @@ -145,6 +150,14 @@ const IssuesTable: React.FC = ({ Deadline )} + {showPullRequestCount && ( + + Open PRs + + )} @@ -191,6 +204,19 @@ const IssuesTable: React.FC = ({ className="gap-1 lg:gap-2" /> + {/* PR Count */} + {showPullRequestCount && ( + + {issue.pullRequestCount !== undefined && issue.pullRequestCount > 0 ? ( + + + {issue.pullRequestCount} + + ) : ( + + )} + + )} {/* Assignee */} {showAssignee && ( diff --git a/frontend/src/components/MenteeIssues.tsx b/frontend/src/components/MenteeIssues.tsx index 1ad1084ed8..a025849703 100644 --- a/frontend/src/components/MenteeIssues.tsx +++ b/frontend/src/components/MenteeIssues.tsx @@ -1,6 +1,7 @@ import type React from 'react' import { useState } from 'react' import { FaBug, FaCheckCircle, FaClock } from 'react-icons/fa' +import { FaCodePullRequest } from 'react-icons/fa6' import { IconWrapper } from 'wrappers/IconWrapper' import type { Issue } from 'types/issue' import { formatDate } from 'utils/dateFormatter' @@ -81,10 +82,17 @@ const MenteeIssues: React.FC = ({ openIssues, closedIssues, m
)} -
+
#{issue.number} Created: {formatDate(issue.createdAt)} {issue.updatedAt && Updated: {formatDate(issue.updatedAt)}} + {!!issue.pullRequests?.length && ( + + + {issue.pullRequests.length} open PR + {issue.pullRequests.length === 1 ? '' : 's'} + + )}
diff --git a/frontend/src/types/card.ts b/frontend/src/types/card.ts index 2c21e4c877..6e909d358e 100644 --- a/frontend/src/types/card.ts +++ b/frontend/src/types/card.ts @@ -26,6 +26,7 @@ export type CardProps = { level?: Level projectLink?: string projectName?: string + pullRequestCount?: number social?: { title: string; icon: IconType; url: string }[] summary: string title: string