import { useState } from "react"; import { Link, useParams, useSearchParams } from "react-router"; import { useQuery } from "@tanstack/react-query"; import { client, type PullRequestResponse } from "@/client"; import { Circle, CircleDot, GitMerge, GitPullRequest, Search, Plus, } from "lucide-react"; import { Input } from "@/components/ui/input"; import { cn } from "@/lib/utils"; import { workspaceColor, workspaceInitial } from "@/components/shell/shared"; import PaginationBar from "./pagination"; function formatDate(date: string) { const now = new Date(); const d = new Date(date); const diff = now.getTime() - d.getTime(); const days = Math.floor(diff / 86400000); if (days < 1) return "today"; if (days === 1) return "1 day ago"; if (days < 30) return `${days} days ago`; if (days < 365) return `${Math.floor(days / 30)}mo ago`; return `${Math.floor(days / 365)}y ago`; } function AuthorAvatar({ author }: { author: { username: string; avatar_url?: string | null; display_name?: string | null } }) { const name = author.display_name ?? author.username; return ( {author.avatar_url ? ( {name ) : ( workspaceInitial(name) )} ); } function LabelBadge({ label }: { label: { name: string; color: string } }) { return ( {label.name} ); } function PullStateIcon({ pr }: { pr: PullRequestResponse }) { if (pr.merged_at) { return ; } if (pr.state === "closed") { return ; } return ; } function PullRow({ pr, projectName, repoName }: { pr: PullRequestResponse; projectName: string; repoName: string }) { return (
{pr.title} # {pr.number} {pr.draft && ( Draft )} {pr.labels.map((label) => )}
{pr.author.display_name ?? pr.author.username} opened {formatDate(pr.created_at)} {pr.source_branch} → {pr.target_branch}
); } const LIMIT = 20; export default function PullsTab() { const { projectName = "", repoName = "" } = useParams(); const [stateFilter, setStateFilter] = useState("open"); const [search, setSearch] = useState(""); const [pullsSearchParams, setPullsSearchParams] = useSearchParams(); const offset = Number(pullsSearchParams.get("offset") ?? "0"); const setOffset = (n: number) => { const next = new URLSearchParams(pullsSearchParams); if (n <= 0) next.delete("offset"); else next.set("offset", String(n)); setPullsSearchParams(next, { replace: true }); }; const { data: prs = [], isLoading } = useQuery({ queryKey: ["repo", projectName, repoName, "pulls", stateFilter, offset], queryFn: async () => { const res = await client.pullRequestListPrs(projectName, repoName, { state: stateFilter === "all" ? undefined : stateFilter, offset, limit: LIMIT }); return res.data; }, enabled: Boolean(projectName) && Boolean(repoName), retry: false, }); const filteredPrs = search ? prs.filter((pr) => pr.title.toLowerCase().includes(search.toLowerCase()) || String(pr.number).includes(search)) : prs; const openCount = prs.filter((pr) => pr.state === "open" && !pr.merged_at).length; const closedCount = prs.filter((pr) => pr.state === "closed" || pr.merged_at).length; return (
New PR
setSearch(e.target.value)} />
{isLoading ? (
{Array.from({ length: 3 }).map((_, i) =>
)}
) : filteredPrs.length > 0 ? ( <>
{filteredPrs.map((pr) => )}
= LIMIT} onPrev={() => setOffset(Math.max(0, offset - LIMIT))} onNext={() => setOffset(offset + LIMIT)} /> ) : (

{stateFilter === "open" ? "No open pull requests" : stateFilter === "closed" ? "No closed pull requests" : "No pull requests yet"}

)}
); }