import type { ReactNode } from "react"; import type { Issue } from "@paperclipai/shared"; import { Columns3 } from "lucide-react"; import { pickTextColorForPillBg } from "@/lib/color-contrast"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { formatAssigneeUserLabel } from "../lib/assignees"; import type { InboxIssueColumn } from "../lib/inbox"; import { cn } from "../lib/utils"; import { timeAgo } from "../lib/timeAgo"; import { Identity } from "./Identity"; import { StatusIcon } from "./StatusIcon"; export const issueTrailingColumns: InboxIssueColumn[] = ["assignee", "project", "workspace", "parent", "labels", "updated"]; const issueColumnLabels: Record = { status: "Status", id: "ID", assignee: "Assignee", project: "Project", workspace: "Workspace", parent: "Parent issue", labels: "Tags", updated: "Last updated", }; const issueColumnDescriptions: Record = { status: "Issue state chip on the left edge.", id: "Ticket identifier like PAP-1009.", assignee: "Assigned agent or board user.", project: "Linked project pill with its color.", workspace: "Execution or project workspace used for the issue.", parent: "Parent issue identifier and title.", labels: "Issue labels and tags.", updated: "Latest visible activity time.", }; export function issueActivityText(issue: Issue): string { return `Updated ${timeAgo(issue.lastActivityAt ?? issue.lastExternalCommentAt ?? issue.updatedAt)}`; } function issueTrailingGridTemplate(columns: InboxIssueColumn[]): string { return columns .map((column) => { if (column === "assignee") return "minmax(6rem, 8rem)"; if (column === "project") return "minmax(4.5rem, 7rem)"; if (column === "workspace") return "minmax(6rem, 9rem)"; if (column === "parent") return "minmax(3.5rem, 5.5rem)"; if (column === "labels") return "minmax(3rem, 6rem)"; return "minmax(3.5rem, 4.5rem)"; }) .join(" "); } export function IssueColumnPicker({ availableColumns, visibleColumnSet, onToggleColumn, onResetColumns, title, iconOnly = false, }: { availableColumns: InboxIssueColumn[]; visibleColumnSet: ReadonlySet; onToggleColumn: (column: InboxIssueColumn, enabled: boolean) => void; onResetColumns: () => void; title: string; iconOnly?: boolean; }) { return (
Desktop issue rows
{title}
{availableColumns.map((column) => ( event.preventDefault()} onCheckedChange={(checked) => onToggleColumn(column, checked === true)} className="items-start rounded-lg px-3 py-2.5 pl-8" > {issueColumnLabels[column]} {issueColumnDescriptions[column]} ))} Reset defaults status, id, updated
); } export function InboxIssueMetaLeading({ issue, isLive, showStatus = true, showIdentifier = true, statusSlot, checklistStepNumber = null, }: { issue: Issue; isLive: boolean; showStatus?: boolean; showIdentifier?: boolean; statusSlot?: ReactNode; checklistStepNumber?: number | string | null; }) { return ( <> {showStatus ? ( {statusSlot ?? } ) : null} {checklistStepNumber !== null ? ( ) : null} {showIdentifier ? ( {issue.identifier ?? issue.id.slice(0, 8)} ) : null} {isLive && ( Live )} ); } export function InboxIssueTrailingColumns({ issue, columns, projectName, projectColor, workspaceId, workspaceName, assigneeName, assigneeUserName, assigneeUserAvatarUrl, currentUserId, parentIdentifier, parentTitle, assigneeContent, onFilterWorkspace, }: { issue: Issue; columns: InboxIssueColumn[]; projectName: string | null; projectColor: string | null; workspaceId?: string | null; workspaceName: string | null; assigneeName: string | null; assigneeUserName?: string | null; assigneeUserAvatarUrl?: string | null; currentUserId: string | null; parentIdentifier: string | null; parentTitle: string | null; assigneeContent?: ReactNode; onFilterWorkspace?: (workspaceId: string) => void; }) { const activityText = timeAgo(issue.lastActivityAt ?? issue.lastExternalCommentAt ?? issue.updatedAt); const userLabel = assigneeUserName ?? formatAssigneeUserLabel(issue.assigneeUserId, currentUserId) ?? "User"; return ( {columns.map((column) => { if (column === "assignee") { if (assigneeContent) { return {assigneeContent}; } if (issue.assigneeAgentId) { return ( ); } if (issue.assigneeUserId) { return ( ); } return ( Unassigned ); } if (column === "project") { if (projectName) { const accentColor = projectColor ?? "#64748b"; return ( {projectName} ); } return ( No project ); } if (column === "labels") { if ((issue.labels ?? []).length > 0) { return ( {(issue.labels ?? []).slice(0, 2).map((label) => ( {label.name} ))} {(issue.labels ?? []).length > 2 ? ( +{(issue.labels ?? []).length - 2} ) : null} ); } return