import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { approvalsApi } from "../api/approvals"; import { dashboardApi } from "../api/dashboard"; import { issuesApi } from "../api/issues"; import { agentsApi } from "../api/agents"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { queryKeys } from "../lib/queryKeys"; import { StatusIcon } from "../components/StatusIcon"; import { PriorityIcon } from "../components/PriorityIcon"; import { EmptyState } from "../components/EmptyState"; import { timeAgo } from "../lib/timeAgo"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { Inbox as InboxIcon, Shield, AlertTriangle, Clock, ExternalLink, } from "lucide-react"; import { Identity } from "../components/Identity"; import type { Issue } from "@paperclip/shared"; const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours function getStaleIssues(issues: Issue[]): Issue[] { const now = Date.now(); return issues .filter( (i) => ["in_progress", "todo"].includes(i.status) && now - new Date(i.updatedAt).getTime() > STALE_THRESHOLD_MS ) .sort( (a, b) => new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime() ); } export function Inbox() { const { selectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const navigate = useNavigate(); const queryClient = useQueryClient(); const [actionError, setActionError] = useState(null); const { data: agents } = useQuery({ queryKey: queryKeys.agents.list(selectedCompanyId!), queryFn: () => agentsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); useEffect(() => { setBreadcrumbs([{ label: "Inbox" }]); }, [setBreadcrumbs]); const { data: approvals, isLoading, error } = useQuery({ queryKey: queryKeys.approvals.list(selectedCompanyId!), queryFn: () => approvalsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const { data: dashboard } = useQuery({ queryKey: queryKeys.dashboard(selectedCompanyId!), queryFn: () => dashboardApi.summary(selectedCompanyId!), enabled: !!selectedCompanyId, }); const { data: issues } = useQuery({ queryKey: queryKeys.issues.list(selectedCompanyId!), queryFn: () => issuesApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const staleIssues = issues ? getStaleIssues(issues) : []; const agentName = (id: string | null) => { if (!id || !agents) return null; const agent = agents.find((a) => a.id === id); return agent?.name ?? null; }; const approveMutation = useMutation({ mutationFn: (id: string) => approvalsApi.approve(id), onSuccess: (_approval, id) => { queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!) }); navigate(`/approvals/${id}?resolved=approved`); }, onError: (err) => { setActionError(err instanceof Error ? err.message : "Failed to approve"); }, }); const rejectMutation = useMutation({ mutationFn: (id: string) => approvalsApi.reject(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!) }); }, onError: (err) => { setActionError(err instanceof Error ? err.message : "Failed to reject"); }, }); if (!selectedCompanyId) { return ; } const actionableApprovals = (approvals ?? []).filter( (approval) => approval.status === "pending" || approval.status === "revision_requested", ); const hasActionableApprovals = actionableApprovals.length > 0; const hasAlerts = dashboard && (dashboard.agents.error > 0 || dashboard.costs.monthUtilizationPercent >= 80); const hasStale = staleIssues.length > 0; const hasContent = hasActionableApprovals || hasAlerts || hasStale; return (
{isLoading &&

Loading...

} {error &&

{error.message}

} {actionError &&

{actionError}

} {!isLoading && !hasContent && ( )} {/* Pending Approvals */} {hasActionableApprovals && (

Approvals

{actionableApprovals.map((approval) => (
{approval.type.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} {timeAgo(approval.createdAt)}
))}
)} {/* Alerts */} {hasAlerts && ( <> {hasActionableApprovals && }

Alerts

{dashboard!.agents.error > 0 && (
navigate("/agents")} > {dashboard!.agents.error}{" "} {dashboard!.agents.error === 1 ? "agent has" : "agents have"} errors
)} {dashboard!.costs.monthUtilizationPercent >= 80 && (
navigate("/costs")} > Budget at{" "} {dashboard!.costs.monthUtilizationPercent}% {" "} utilization this month
)}
)} {/* Stale Work */} {hasStale && ( <> {(hasActionableApprovals || hasAlerts) && }

Stale Work

{staleIssues.map((issue) => (
navigate(`/issues/${issue.id}`)} > {issue.identifier ?? issue.id.slice(0, 8)} {issue.title} {issue.assigneeAgentId && (() => { const name = agentName(issue.assigneeAgentId); return name ? : {issue.assigneeAgentId.slice(0, 8)}; })()} updated {timeAgo(issue.updatedAt)}
))}
)}
); }