BugRow.tsx

 1import { Link } from 'react-router-dom'
 2import { MessageSquare, CircleDot, CircleCheck } from 'lucide-react'
 3import { formatDistanceToNow } from 'date-fns'
 4import { LabelBadge } from './LabelBadge'
 5import { Status } from '@/__generated__/graphql'
 6
 7interface BugRowProps {
 8  id: string
 9  humanId: string
10  status: Status
11  title: string
12  labels: Array<{ name: string; color: { R: number; G: number; B: number } }>
13  author: { humanId: string; displayName: string; avatarUrl?: string | null }
14  createdAt: string
15  commentCount: number
16  /** Current repo slug, used to build /:repo/issues/:id and /:repo/user/:id links. */
17  repo: string | null
18  onLabelClick?: (name: string) => void
19}
20
21// Single row in the issue list. Shows status icon, title, labels, author and
22// comment count. Labels are clickable to filter the list by that label.
23export function BugRow({
24  humanId,
25  status,
26  title,
27  labels,
28  author,
29  createdAt,
30  commentCount,
31  repo,
32  onLabelClick,
33}: BugRowProps) {
34  const isOpen = status === Status.Open
35  const StatusIcon = isOpen ? CircleDot : CircleCheck
36
37  const issueHref = repo ? `/${repo}/issues/${humanId}` : `/issues/${humanId}`
38  const authorHref = repo ? `/${repo}/user/${author.humanId}` : `/user/${author.humanId}`
39
40  return (
41    <div className="flex items-start gap-3 border-b border-border px-4 py-3 last:border-0 hover:bg-muted/30">
42      <StatusIcon
43        className={
44          isOpen
45            ? 'mt-0.5 size-4 shrink-0 text-green-600 dark:text-green-400'
46            : 'mt-0.5 size-4 shrink-0 text-purple-600 dark:text-purple-400'
47        }
48      />
49
50      <div className="min-w-0 flex-1">
51        <div className="flex flex-wrap items-baseline gap-2">
52          <Link
53            to={issueHref}
54            className="font-medium text-foreground hover:text-primary hover:underline"
55          >
56            {title}
57          </Link>
58          {labels.map((label) => (
59            <LabelBadge key={label.name} name={label.name} color={label.color} onClick={onLabelClick} />
60          ))}
61        </div>
62        <p className="mt-0.5 text-xs text-muted-foreground">
63          #{humanId} opened {formatDistanceToNow(new Date(createdAt), { addSuffix: true })} by{' '}
64          <Link to={authorHref} className="hover:underline">
65            {author.displayName}
66          </Link>
67        </p>
68      </div>
69
70      {commentCount > 0 && (
71        <div className="flex shrink-0 items-center gap-1 text-xs text-muted-foreground">
72          <MessageSquare className="size-3.5" />
73          {commentCount}
74        </div>
75      )}
76    </div>
77  )
78}