import { useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { AlertTriangle, CheckCircle2 } from "lucide-react"; import type { Issue } from "@paperclipai/shared"; import { issuesApi } from "../api/issues"; import { queryKeys } from "../lib/queryKeys"; import { cn } from "../lib/utils"; import { applyIssueFilters, type IssueFilterState, type IssueFilterWorkspaceContext } from "../lib/issue-filters"; import { blockedRowMatchesSearch, buildBlockedInboxRows, formatStoppedAge, groupBlockedInboxRows, sortBlockedInboxRows, type BlockedInboxGroupBy, type BlockedInboxIssueRow, type BlockedInboxSort, } from "../lib/blockedInbox"; import { BlockedReasonChip } from "./BlockedReasonChip"; import { IssueGroupHeader } from "./IssueGroupHeader"; import { IssueRow } from "./IssueRow"; import { Identity } from "./Identity"; import { StatusIcon } from "./StatusIcon"; import { Button } from "@/components/ui/button"; interface BlockedInboxViewProps { companyId: string; searchQuery: string; agentNameById: ReadonlyMap; userLabelById?: ReadonlyMap; issueLinkState: unknown; groupBy: BlockedInboxGroupBy; sortBy: BlockedInboxSort; issueFilters: IssueFilterState; currentUserId: string | null; liveIssueIds: ReadonlySet; workspaceFilterContext: IssueFilterWorkspaceContext; showStatusColumn: boolean; showIdentifierColumn: boolean; showUpdatedColumn: boolean; } const BLOCKED_LIST_LIMIT = 200; export function BlockedInboxView({ companyId, searchQuery, agentNameById, userLabelById, issueLinkState, groupBy, sortBy, issueFilters, currentUserId, liveIssueIds, workspaceFilterContext, showStatusColumn, showIdentifierColumn, showUpdatedColumn, }: BlockedInboxViewProps) { const [collapsedVariants, setCollapsedVariants] = useState>(() => new Set()); const { data: issues = [] as Issue[], isLoading, isFetching, error, refetch, } = useQuery({ queryKey: queryKeys.issues.listBlockedAttention(companyId), queryFn: () => issuesApi.list(companyId, { attention: "blocked", includeBlockedInboxAttention: true, includeBlockedBy: true, limit: BLOCKED_LIST_LIMIT, }), }); const allRows = useMemo(() => buildBlockedInboxRows(issues), [issues]); const filteredRows = useMemo( () => allRows.filter((row) => blockedRowMatchesSearch(row, searchQuery)), [allRows, searchQuery], ); const issueFilteredRows = useMemo(() => { const visibleIssueIds = new Set( applyIssueFilters( filteredRows.map((row) => row.issue), issueFilters, currentUserId, true, liveIssueIds, workspaceFilterContext, ).map((issue) => issue.id), ); return filteredRows.filter((row) => visibleIssueIds.has(row.issue.id)); }, [currentUserId, filteredRows, issueFilters, liveIssueIds, workspaceFilterContext]); const sortedRows = useMemo(() => sortBlockedInboxRows(issueFilteredRows, sortBy), [issueFilteredRows, sortBy]); const groups = useMemo( () => groupBlockedInboxRows(issueFilteredRows, sortBy), [issueFilteredRows, sortBy], ); const toggleVariant = (variant: string) => { setCollapsedVariants((prev) => { const next = new Set(prev); if (next.has(variant)) next.delete(variant); else next.add(variant); return next; }); }; if (isLoading) { return (
{Array.from({ length: 3 }).map((_, groupIdx) => (
{Array.from({ length: 2 }).map((__, rowIdx) => (
))}
))}
); } if (error) { const message = error instanceof Error ? error.message : "Couldn't load the Blocked tab."; return (
); } if (allRows.length === 0) { return (

No work is stopped.

Issues that need a decision, recovery, or external action will appear here.

); } if (groups.length === 0) { return (
No stopped items match your search.
); } return (
{groupBy === "none" ? ( sortedRows.map((row) => ( )) ) : ( groups.map((group) => { const isCollapsed = collapsedVariants.has(group.variant); return (
toggleVariant(group.variant)} />
{!isCollapsed && (
{group.rows.map((row) => ( ))}
)}
); }) )}
); } interface BlockedInboxRowProps { row: BlockedInboxIssueRow; issueLinkState: unknown; agentNameById: ReadonlyMap; userLabelById?: ReadonlyMap; showStatusColumn: boolean; showIdentifierColumn: boolean; showUpdatedColumn: boolean; } function resolveOwnerName( row: BlockedInboxIssueRow, agentNameById: ReadonlyMap, userLabelById?: ReadonlyMap, ): { label: string | null; isAgent: boolean } { const owner = row.attention.owner; if (owner.label) return { label: owner.label, isAgent: owner.type === "agent" }; if (owner.agentId) { return { label: agentNameById.get(owner.agentId) ?? null, isAgent: true }; } if (owner.userId) { return { label: userLabelById?.get(owner.userId) ?? null, isAgent: false }; } return { label: null, isAgent: false }; } function BlockedInboxRow({ row, issueLinkState, agentNameById, userLabelById, showStatusColumn, showIdentifierColumn, showUpdatedColumn, }: BlockedInboxRowProps) { const { label: ownerName, isAgent } = resolveOwnerName(row, agentNameById, userLabelById); const stoppedAge = formatStoppedAge(row.attention.stoppedSinceAt); const desktopTrailing = ( {ownerName ? ( ) : ( ); const mobileMeta = ( {stoppedAge} {ownerName ? ( <> {ownerName} ) : null} ); return ( } mobileLeading={ } titleSuffix={ } mobileMeta={mobileMeta} desktopTrailing={desktopTrailing} /> ); } function BlockedRowDesktopMeta({ row, showStatusColumn, showIdentifierColumn, }: { row: BlockedInboxIssueRow; showStatusColumn: boolean; showIdentifierColumn: boolean; }) { const identifier = row.issue.identifier ?? row.issue.id.slice(0, 8); return ( {showStatusColumn ? : null} {showIdentifierColumn ? {identifier} : null} ); }