import { useCallback, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { approvalsApi } from "../api/approvals"; import { dashboardApi } from "../api/dashboard"; import { issuesApi } from "../api/issues"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { useAgents } from "../hooks/useAgents"; import { useApi } from "../hooks/useApi"; import { StatusBadge } from "../components/StatusBadge"; 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 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 [actionError, setActionError] = useState(null); const { data: agents } = useAgents(selectedCompanyId); useEffect(() => { setBreadcrumbs([{ label: "Inbox" }]); }, [setBreadcrumbs]); const approvalsFetcher = useCallback(() => { if (!selectedCompanyId) return Promise.resolve([]); return approvalsApi.list(selectedCompanyId, "pending"); }, [selectedCompanyId]); const dashboardFetcher = useCallback(() => { if (!selectedCompanyId) return Promise.resolve(null); return dashboardApi.summary(selectedCompanyId); }, [selectedCompanyId]); const issuesFetcher = useCallback(() => { if (!selectedCompanyId) return Promise.resolve([]); return issuesApi.list(selectedCompanyId); }, [selectedCompanyId]); const { data: approvals, loading, error, reload } = useApi(approvalsFetcher); const { data: dashboard } = useApi(dashboardFetcher); const { data: issues } = useApi(issuesFetcher); 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; }; async function approve(id: string) { setActionError(null); try { await approvalsApi.approve(id); reload(); } catch (err) { setActionError(err instanceof Error ? err.message : "Failed to approve"); } } async function reject(id: string) { setActionError(null); try { await approvalsApi.reject(id); reload(); } catch (err) { setActionError(err instanceof Error ? err.message : "Failed to reject"); } } if (!selectedCompanyId) { return ; } const hasApprovals = approvals && approvals.length > 0; const hasAlerts = dashboard && (dashboard.agents.error > 0 || dashboard.costs.monthUtilizationPercent >= 80); const hasStale = staleIssues.length > 0; const hasContent = hasApprovals || hasAlerts || hasStale; return (

Inbox

{loading &&

Loading...

} {error &&

{error.message}

} {actionError &&

{actionError}

} {!loading && !hasContent && ( )} {/* Pending Approvals */} {hasApprovals && (

Approvals

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

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 && ( <> {(hasApprovals || hasAlerts) && }

Stale Work

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