BugRow.tsx

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