// Paginated commit history grouped by calendar date. Each row links to the // commit detail page. Used in CodePage's "History" view. import { gql } from "@apollo/client"; import { useQuery } from "@apollo/client/react"; import { Link } from "@tanstack/react-router"; import { formatDistanceToNow } from "date-fns"; import { GitCommit } from "lucide-react"; import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { useRepo } from "@/lib/repo"; const COMMITS_QUERY = gql` query CommitList($repo: String, $ref: String!, $path: String, $after: String, $first: Int) { repository(ref: $repo) { commits(ref: $ref, path: $path, after: $after, first: $first) { nodes { hash shortHash message authorName date } pageInfo { hasNextPage endCursor } } } } `; const PAGE_SIZE = 30; interface CommitListQueryData { repository: { commits: { nodes: CommitNode[]; pageInfo: { hasNextPage: boolean; endCursor: string | null }; } | null; } | null; } interface CommitListProps { ref_: string; path?: string; } type CommitNode = { hash: string; shortHash: string; message: string; authorName: string; date: string; }; export function CommitList({ ref_, path }: CommitListProps) { const repo = useRepo(); const [cursor, setCursor] = useState(null); const [allCommits, setAllCommits] = useState([]); const { data, loading, error, fetchMore } = useQuery(COMMITS_QUERY, { variables: { repo, ref: ref_, path: path ?? null, after: null, first: PAGE_SIZE }, skip: !ref_, }); useEffect(() => { const nodes = data?.repository?.commits?.nodes ?? []; setAllCommits(nodes); setCursor(data?.repository?.commits?.pageInfo?.endCursor ?? null); }, [data]); const hasMore = !!cursor && allCommits.length > 0 && allCommits.length % PAGE_SIZE === 0; const [loadingMore, setLoadingMore] = useState(false); function loadMore() { if (!cursor) return; setLoadingMore(true); void fetchMore({ variables: { after: cursor }, }) .then((result) => { const newNodes = result.data?.repository?.commits?.nodes ?? []; setAllCommits((prev) => [...prev, ...newNodes]); setCursor(result.data?.repository?.commits?.pageInfo?.endCursor ?? null); }) .finally(() => setLoadingMore(false)); } if (loading) return ; if (error) { return (
{error.message}
); } const groups = groupByDate(allCommits); return (
{groups.map(([date, group]) => (

Commits on {date}

{group.map((commit) => ( ))}
))} {hasMore && (
)}
); } function CommitRow({ commit, repo }: { commit: CommitNode; repo: string | null }) { const commitPath = repo ? `/${repo}/commit/${commit.hash}` : `/commit/${commit.hash}`; return (
{commit.message}

{commit.authorName} ·{" "} {formatDistanceToNow(new Date(commit.date), { addSuffix: true })}

{commit.shortHash}
); } function groupByDate(commits: CommitNode[]): [string, CommitNode[]][] { const map = new Map(); for (const c of commits) { const date = new Date(c.date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }); const group = map.get(date) ?? []; group.push(c); map.set(date, group); } return Array.from(map.entries()); } function CommitListSkeleton() { return (
{Array.from({ length: 2 }).map((_, g) => (
{Array.from({ length: 4 }).map((_c, i) => (
))}
))}
); }