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 ? (
) : (
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 (
{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"}
)}
);
}