Repository Public
1'use client';2import { GitBranch, RefreshCw } from 'lucide-react';3import { useGitStatus } from '@/lib/queries';4import { IconButton } from '@/components/ui/button';5import { EmptyState, ErrorState, Spinner } from '@/components/ui/misc';6import { cn } from '@/lib/cn';78const LETTER: Record<string, string> = { M: 'text-warning', A: 'text-success', D: 'text-error', R: 'text-accent', '?': 'text-success', U: 'text-error' };910/** Git status of a project. Clicking a changed file opens its diff. */11export function GitPanel({ projectId, onOpenDiff }: { projectId: string; onOpenDiff: (path: string) => void }) {12 const q = useGitStatus(projectId);13 if (q.isPending) return <div className="flex justify-center p-6"><Spinner /></div>;14 if (q.isError) return <ErrorState error={q.error} onRetry={() => q.refetch()} />;15 const g = q.data;16 if (!g.isRepo) return <EmptyState icon={<GitBranch />} title="Not a Git repository" description="This project directory is not under version control." />;17 return (18 <div className="flex h-full flex-col" data-testid="git-panel">19 <div className="flex items-center gap-2 border-b border-line px-3 py-2 text-xs">20 <GitBranch className="size-3.5 text-muted" aria-hidden />21 <span className="font-mono text-text">{g.branch ?? 'detached HEAD'}</span>22 <span className="tnum text-muted">↑{g.ahead} ↓{g.behind}</span>23 <span className="flex-1" />24 <IconButton label="Refresh Git status" size="sm" onClick={() => q.refetch()}><RefreshCw className={cn('size-3.5', q.isFetching && 'animate-spin')} /></IconButton>25 </div>26 {g.files.length === 0 ? (27 <EmptyState title="Working tree clean" description="No changed files." />28 ) : (29 <ul className="min-h-0 flex-1 overflow-y-auto py-1">30 {g.files.map((f) => {31 const letter = (f.worktree.trim() || f.index.trim() || '?')[0] ?? '?';32 return (33 <li key={f.path}>34 <button onClick={() => onOpenDiff(f.path)} data-testid="git-file" className="flex min-h-7 w-full items-center gap-2 px-3 text-left text-xs hover:bg-surface-2 pointer-coarse:min-h-11">35 <span className={cn('w-4 shrink-0 text-center font-mono font-semibold', LETTER[letter] ?? 'text-text-2')} title={`index “${f.index}”, worktree “${f.worktree}”`}>{letter === '?' ? 'U' : letter}</span>36 <span className="truncate font-mono text-text-2">{f.path}</span>37 </button>38 </li>39 );40 })}41 </ul>42 )}43 </div>44 );45}46