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 { agentsApi } from "../api/agents";
import { useCompany } from "../context/CompanyContext";
import { useBreadcrumbs } from "../context/BreadcrumbContext";
import { queryKeys } from "../lib/queryKeys";
import { timeAgo } from "../lib/timeAgo";
import { cn } from "../lib/utils";
import { Button } from "@/components/ui/button";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { CheckCircle2, XCircle, Clock, ShieldCheck } from "lucide-react";
import { Identity } from "../components/Identity";
import { typeLabel, typeIcon, defaultTypeIcon, ApprovalPayloadRenderer } from "../components/ApprovalPayload";
import type { Approval, Agent } from "@paperclip/shared";
type StatusFilter = "pending" | "all";
function statusIcon(status: string) {
if (status === "approved") return ;
if (status === "rejected") return ;
if (status === "revision_requested") return ;
if (status === "pending") return ;
return null;
}
function ApprovalCard({
approval,
requesterAgent,
onApprove,
onReject,
onOpen,
isPending,
}: {
approval: Approval;
requesterAgent: Agent | null;
onApprove: () => void;
onReject: () => void;
onOpen: () => void;
isPending: boolean;
}) {
const Icon = typeIcon[approval.type] ?? defaultTypeIcon;
const label = typeLabel[approval.type] ?? approval.type;
return (
{/* Header */}
{label}
{requesterAgent && (
requested by
)}
{statusIcon(approval.status)}
{approval.status}
ยท {timeAgo(approval.createdAt)}
{/* Payload */}
{/* Decision note */}
{approval.decisionNote && (
Note: {approval.decisionNote}
)}
{/* Actions */}
{(approval.status === "pending" || approval.status === "revision_requested") && (
)}
);
}
export function Approvals() {
const { selectedCompanyId } = useCompany();
const { setBreadcrumbs } = useBreadcrumbs();
const queryClient = useQueryClient();
const navigate = useNavigate();
const [statusFilter, setStatusFilter] = useState("pending");
const [actionError, setActionError] = useState(null);
useEffect(() => {
setBreadcrumbs([{ label: "Approvals" }]);
}, [setBreadcrumbs]);
const { data, isLoading, error } = useQuery({
queryKey: queryKeys.approvals.list(selectedCompanyId!),
queryFn: () => approvalsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId,
});
const { data: agents } = useQuery({
queryKey: queryKeys.agents.list(selectedCompanyId!),
queryFn: () => agentsApi.list(selectedCompanyId!),
enabled: !!selectedCompanyId,
});
const approveMutation = useMutation({
mutationFn: (id: string) => approvalsApi.approve(id),
onSuccess: (_approval, id) => {
setActionError(null);
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: () => {
setActionError(null);
queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!) });
},
onError: (err) => {
setActionError(err instanceof Error ? err.message : "Failed to reject");
},
});
const filtered = (data ?? []).filter(
(a) => statusFilter === "all" || a.status === "pending" || a.status === "revision_requested",
);
const pendingCount = (data ?? []).filter(
(a) => a.status === "pending" || a.status === "revision_requested",
).length;
if (!selectedCompanyId) {
return Select a company first.
;
}
return (
setStatusFilter(v as StatusFilter)}>
Pending
{pendingCount > 0 && (
{pendingCount}
)}
All
{isLoading &&
Loading...
}
{error &&
{error.message}
}
{actionError &&
{actionError}
}
{!isLoading && filtered.length === 0 && (
{statusFilter === "pending" ? "No pending approvals." : "No approvals yet."}
)}
{filtered.length > 0 && (
{filtered.map((approval) => (
a.id === approval.requestedByAgentId) ?? null : null}
onApprove={() => approveMutation.mutate(approval.id)}
onReject={() => rejectMutation.mutate(approval.id)}
onOpen={() => navigate(`/approvals/${approval.id}`)}
isPending={approveMutation.isPending || rejectMutation.isPending}
/>
))}
)}
);
}