|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import { ArrowDown, ArrowUp, Star } from "lucide-react"; |
| 4 | +import { useTranslation } from "@/components/providers/language-provider"; |
| 5 | +import type { UserResult } from "@/features/developer/types"; |
| 6 | +import { |
| 7 | + formatLanguageMatch, |
| 8 | + LanguageBreakdown, |
| 9 | + SelectedLanguageRow, |
| 10 | + StatChip, |
| 11 | +} from "./work-card-helpers"; |
| 12 | + |
| 13 | +export type PullRequestCardItemProps = { |
| 14 | + pr: UserResult["topPullRequests"][number]; |
| 15 | + rankIndex?: number; |
| 16 | + selectedLanguages?: string[]; |
| 17 | + showRank?: boolean; |
| 18 | + className?: string; |
| 19 | +}; |
| 20 | + |
| 21 | +export function PullRequestCardItem({ |
| 22 | + pr, |
| 23 | + rankIndex, |
| 24 | + selectedLanguages, |
| 25 | + showRank = true, |
| 26 | + className = "", |
| 27 | +}: PullRequestCardItemProps) { |
| 28 | + const { t } = useTranslation(); |
| 29 | + const prTitle = pr.title || t("untitled"); |
| 30 | + const targetRepo = pr.repo || t("unknown.repo"); |
| 31 | + |
| 32 | + return ( |
| 33 | + <article |
| 34 | + className={`rounded-xl border border-border/80 bg-gradient-to-br from-card via-card to-muted/25 p-4 transition-all duration-200 hover:border-primary/40 hover:shadow-sm ${className}`} |
| 35 | + > |
| 36 | + <div className="flex items-start justify-between gap-3 sm:gap-4"> |
| 37 | + <div className="min-w-0 flex-1"> |
| 38 | + <div className="flex items-start gap-2"> |
| 39 | + {showRank && typeof rankIndex === "number" ? ( |
| 40 | + <span className="mt-0.5 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-bold text-muted-foreground"> |
| 41 | + #{rankIndex + 1} |
| 42 | + </span> |
| 43 | + ) : null} |
| 44 | + |
| 45 | + {pr.url ? ( |
| 46 | + <a |
| 47 | + href={pr.url} |
| 48 | + target="_blank" |
| 49 | + rel="noopener noreferrer" |
| 50 | + className="break-words font-semibold leading-snug text-primary hover:underline" |
| 51 | + aria-label={t("a11y.openPullRequest", { title: prTitle })} |
| 52 | + > |
| 53 | + {prTitle} |
| 54 | + </a> |
| 55 | + ) : ( |
| 56 | + <p className="break-words font-semibold leading-snug">{prTitle}</p> |
| 57 | + )} |
| 58 | + </div> |
| 59 | + |
| 60 | + <p className="mt-1 break-words text-xs font-medium text-muted-foreground"> |
| 61 | + {t("topwork.inRepo", { repo: targetRepo })} |
| 62 | + </p> |
| 63 | + |
| 64 | + <div className="mt-2.5 flex flex-wrap items-center gap-1.5"> |
| 65 | + <StatChip |
| 66 | + icon={<Star className="h-3 w-3" />} |
| 67 | + label={t("topwork.pr.repo.stars")} |
| 68 | + value={pr.stars ?? 0} |
| 69 | + /> |
| 70 | + <div className="inline-flex items-center gap-1.5 rounded-full border border-border/80 bg-background/80 px-2.5 py-1 text-xs"> |
| 71 | + <span className="inline-flex items-center gap-0.5 font-semibold text-emerald-600 dark:text-emerald-400"> |
| 72 | + <ArrowUp className="h-3 w-3" />+{pr.additions ?? 0} |
| 73 | + </span> |
| 74 | + <span className="text-muted-foreground">/</span> |
| 75 | + <span className="inline-flex items-center gap-0.5 font-semibold text-rose-600 dark:text-rose-400"> |
| 76 | + <ArrowDown className="h-3 w-3" />-{pr.deletions ?? 0} |
| 77 | + </span> |
| 78 | + </div> |
| 79 | + </div> |
| 80 | + |
| 81 | + <LanguageBreakdown topLanguages={pr.topLanguages} /> |
| 82 | + |
| 83 | + {selectedLanguages && selectedLanguages.length > 0 ? ( |
| 84 | + <SelectedLanguageRow |
| 85 | + topLanguages={pr.topLanguages} |
| 86 | + selectedLanguages={selectedLanguages} |
| 87 | + label={t("topwork.selectedLang")} |
| 88 | + /> |
| 89 | + ) : null} |
| 90 | + |
| 91 | + {typeof pr.languageMatch === "number" ? ( |
| 92 | + <p className="mt-2 text-xs text-muted-foreground"> |
| 93 | + {t("language.match")}: {formatLanguageMatch(pr.languageMatch)} |
| 94 | + </p> |
| 95 | + ) : null} |
| 96 | + </div> |
| 97 | + |
| 98 | + <div className="shrink-0 rounded-lg border border-primary/25 bg-primary/5 px-2.5 py-1.5 text-right sm:px-3 sm:py-2"> |
| 99 | + <p className="text-lg font-bold text-primary sm:text-xl">{pr.score ?? 0}</p> |
| 100 | + <p className="text-[10px] text-muted-foreground sm:text-[11px]"> |
| 101 | + {t("comparsion.score")} |
| 102 | + </p> |
| 103 | + </div> |
| 104 | + </div> |
| 105 | + </article> |
| 106 | + ); |
| 107 | +} |
0 commit comments