import { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { activityApi } from "../api/activity"; import { agentsApi } from "../api/agents"; import { issuesApi } from "../api/issues"; import { projectsApi } from "../api/projects"; import { goalsApi } from "../api/goals"; import { useCompany } from "../context/CompanyContext"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { queryKeys } from "../lib/queryKeys"; import { EmptyState } from "../components/EmptyState"; import { Identity } from "../components/Identity"; import { timeAgo } from "../lib/timeAgo"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { History } from "lucide-react"; import type { Agent } from "@paperclip/shared"; // Maps action → verb phrase. When the entity name is available it reads as: // "[Actor] commented on "Fix the bug"" // When not available, it falls back to just the verb. const ACTION_VERBS: Record = { "issue.created": "created", "issue.updated": "updated", "issue.checked_out": "checked out", "issue.released": "released", "issue.comment_added": "commented on", "issue.commented": "commented on", "issue.deleted": "deleted", "agent.created": "created", "agent.updated": "updated", "agent.paused": "paused", "agent.resumed": "resumed", "agent.terminated": "terminated", "agent.key_created": "created API key for", "agent.budget_updated": "updated budget for", "agent.runtime_session_reset": "reset session for", "heartbeat.invoked": "invoked heartbeat for", "heartbeat.cancelled": "cancelled heartbeat for", "approval.created": "requested approval", "approval.approved": "approved", "approval.rejected": "rejected", "project.created": "created", "project.updated": "updated", "project.deleted": "deleted", "goal.created": "created", "goal.updated": "updated", "goal.deleted": "deleted", "cost.reported": "reported cost for", "cost.recorded": "recorded cost for", "company.created": "created", "company.updated": "updated", "company.archived": "archived", "company.budget_updated": "updated budget for", }; function entityLink(entityType: string, entityId: string): string | null { switch (entityType) { case "issue": return `/issues/${entityId}`; case "agent": return `/agents/${entityId}`; case "project": return `/projects/${entityId}`; case "goal": return `/goals/${entityId}`; case "approval": return `/approvals/${entityId}`; default: return null; } } function actorIdentity(actorType: string, actorId: string, agentMap: Map) { if (actorType === "agent") { const agent = agentMap.get(actorId); return ; } if (actorType === "system") return ; return ; } export function Activity() { const { selectedCompanyId } = useCompany(); const { setBreadcrumbs } = useBreadcrumbs(); const navigate = useNavigate(); const [filter, setFilter] = useState("all"); useEffect(() => { setBreadcrumbs([{ label: "Activity" }]); }, [setBreadcrumbs]); const { data, isLoading, error } = useQuery({ queryKey: queryKeys.activity(selectedCompanyId!), queryFn: () => activityApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const { data: agents } = useQuery({ queryKey: queryKeys.agents.list(selectedCompanyId!), queryFn: () => agentsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const { data: issues } = useQuery({ queryKey: queryKeys.issues.list(selectedCompanyId!), queryFn: () => issuesApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const { data: projects } = useQuery({ queryKey: queryKeys.projects.list(selectedCompanyId!), queryFn: () => projectsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const { data: goals } = useQuery({ queryKey: queryKeys.goals.list(selectedCompanyId!), queryFn: () => goalsApi.list(selectedCompanyId!), enabled: !!selectedCompanyId, }); const agentMap = useMemo(() => { const map = new Map(); for (const a of agents ?? []) map.set(a.id, a); return map; }, [agents]); // Unified map: "entityType:entityId" → display name const entityNameMap = useMemo(() => { const map = new Map(); for (const i of issues ?? []) map.set(`issue:${i.id}`, i.title); for (const a of agents ?? []) map.set(`agent:${a.id}`, a.name); for (const p of projects ?? []) map.set(`project:${p.id}`, p.name); for (const g of goals ?? []) map.set(`goal:${g.id}`, g.title); return map; }, [issues, agents, projects, goals]); if (!selectedCompanyId) { return ; } const filtered = data && filter !== "all" ? data.filter((e) => e.entityType === filter) : data; const entityTypes = data ? [...new Set(data.map((e) => e.entityType))].sort() : []; return (
{isLoading &&

Loading...

} {error &&

{error.message}

} {filtered && filtered.length === 0 && ( )} {filtered && filtered.length > 0 && (
{filtered.map((event) => { const link = entityLink(event.entityType, event.entityId); const verb = ACTION_VERBS[event.action] ?? event.action.replace(/[._]/g, " "); const name = entityNameMap.get(`${event.entityType}:${event.entityId}`); return (
navigate(link) : undefined} >
{actorIdentity(event.actorType, event.actorId, agentMap)} {verb} {name && ( {name} )}
{timeAgo(event.createdAt)}
); })}
)}
); }