"use client"; import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { CheckCircle2, CircleDashed, GitMerge, GitPullRequest, GitPullRequestArrow, GitPullRequestClosed, GitPullRequestDraft, TriangleAlert, XCircle, } from "lucide-react"; import { issuePullRequestsOptions, derivePullRequestStatusKind, derivePullRequestProgressSegments, shouldShowPullRequestStats, type PullRequestStatusKind, type PullRequestProgressSegment, } from "@multica/core/github"; import type { GitHubPullRequest, GitHubPullRequestChecksConclusion, GitHubPullRequestState, } from "@multica/core/types"; import { cn } from "@multica/ui/lib/utils"; import { useT } from "../../i18n"; type IssuesT = ReturnType>["t"]; // Keep the existing sidebar density: show the first 3 PR rows inline, then // collapse the rest once the section reaches 4 rows. const PR_LIMIT_BEFORE_COLLAPSE = 4; const STATE_ICON: Record< GitHubPullRequestState, { icon: React.ComponentType<{ className?: string }>; className: string } > = { open: { icon: GitPullRequestArrow, className: "text-emerald-600 dark:text-emerald-400" }, draft: { icon: GitPullRequestDraft, className: "text-muted-foreground" }, merged: { icon: GitMerge, className: "text-violet-600 dark:text-violet-400" }, closed: { icon: GitPullRequestClosed, className: "text-rose-600 dark:text-rose-400" }, }; const CHECKS_ICON: Record< GitHubPullRequestChecksConclusion, { icon: React.ComponentType<{ className?: string }>; className: string } > = { passed: { icon: CheckCircle2, className: "text-emerald-600 dark:text-emerald-400" }, failed: { icon: XCircle, className: "text-rose-600 dark:text-rose-400" }, pending: { icon: CircleDashed, className: "text-amber-600 dark:text-amber-400" }, }; export function PullRequestList({ issueId }: { issueId: string }) { const { t } = useT("issues"); const [expanded, setExpanded] = useState(false); const { data, isLoading } = useQuery(issuePullRequestsOptions(issueId)); const prs = data?.pull_requests ?? []; if (isLoading) { return

{t(($) => $.detail.pull_requests_loading)}

; } if (prs.length === 0) { return (

{t(($) => $.detail.pull_requests_empty)}

); } // Render rule: // - < PR_LIMIT_BEFORE_COLLAPSE: every PR row is visible. // - >= PR_LIMIT_BEFORE_COLLAPSE: first (LIMIT - 1) rows are visible and // the remainder sits behind a toggle. const useCollapse = prs.length >= PR_LIMIT_BEFORE_COLLAPSE; const expandedHead = useCollapse ? prs.slice(0, PR_LIMIT_BEFORE_COLLAPSE - 1) : prs; const collapsedTail = useCollapse ? prs.slice(PR_LIMIT_BEFORE_COLLAPSE - 1) : []; return (
{expandedHead.map((pr) => ( ))} {useCollapse ? (
{expanded ? collapsedTail.map((pr) => ) : null}
) : null}
); } function PullRequestRow({ pr }: { pr: GitHubPullRequest }) { const { t } = useT("issues"); const cfg = STATE_ICON[pr.state] ?? { icon: GitPullRequest, className: "" }; const StateIcon = cfg.icon; const kind = derivePullRequestStatusKind({ state: pr.state, mergeable_state: pr.mergeable_state, checks_failed: pr.checks_failed, checks_pending: pr.checks_pending, checks_passed: pr.checks_passed, }); const segments = derivePullRequestProgressSegments({ state: pr.state, checks_failed: pr.checks_failed, checks_pending: pr.checks_pending, checks_passed: pr.checks_passed, }); const showStats = shouldShowPullRequestStats({ additions: pr.additions, deletions: pr.deletions, changed_files: pr.changed_files, }); const statusText = useStatusText(kind); const draftPrefix = pr.state === "draft"; const stateLabel = getStateLabel(pr.state, t); return (

{pr.title}

{pr.repo_owner}/{pr.repo_name}#{pr.number} · {stateLabel} {pr.author_login ? ` · @${pr.author_login}` : null}

$.detail.pull_request_card_draft_prefix, { status: statusText }) : statusText } statusKind={kind} />
); } function PullRequestRowDetails({ pr, segments, showStats, statusText, statusKind, }: { pr: GitHubPullRequest; segments: PullRequestProgressSegment[] | null; showStats: boolean; statusText: string; statusKind: PullRequestStatusKind; }) { const { t } = useT("issues"); const checksBadge = getChecksBadge(pr, t); const conflictsBadge = getConflictsBadge(pr, t); const isTerminal = statusKind === "closed" || statusKind === "merged"; const showChecksBadge = !isTerminal && !!checksBadge && statusKind !== "checks_failed" && statusKind !== "checks_pending" && statusKind !== "checks_passed"; const showConflictsBadge = !isTerminal && !!conflictsBadge && statusKind !== "conflicts" && statusKind !== "ready"; return (
{showStats ? : null} {statusText} {showChecksBadge ? : null} {showConflictsBadge ? : null}
); } function PullRequestStats({ pr }: { pr: GitHubPullRequest }) { const { t } = useT("issues"); return ( +{pr.additions ?? 0} −{pr.deletions ?? 0} {t(($) => $.detail.pull_request_card_files_count, { count: pr.changed_files ?? 0, })} ); } function PullRequestProgressStrip({ segments, }: { segments: PullRequestProgressSegment[] | null; }) { if (!segments) return null; return (