UI: approval detail page, agent hiring UX, costs breakdown, sidebar badges, and dashboard improvements
Add ApprovalDetail page with comment thread, revision request/resubmit flow, and ApprovalPayload component for structured payload display. Extend AgentDetail with permissions management, config revision history, and duplicate action. Add agent hire dialog with permission-gated access. Rework Costs page with per-agent breakdown table and period filtering. Add sidebar badge counts for pending approvals and inbox items. Enhance Dashboard with live metrics and sparkline trends. Extend Agents list with pending_approval status and bulk actions. Update IssueDetail with approval linking. Various component improvements to MetricCard, InlineEditor, CommentThread, and StatusBadge. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,8 +58,8 @@ export function Inbox() {
|
||||
}, [setBreadcrumbs]);
|
||||
|
||||
const { data: approvals, isLoading, error } = useQuery({
|
||||
queryKey: queryKeys.approvals.list(selectedCompanyId!, "pending"),
|
||||
queryFn: () => approvalsApi.list(selectedCompanyId!, "pending"),
|
||||
queryKey: queryKeys.approvals.list(selectedCompanyId!),
|
||||
queryFn: () => approvalsApi.list(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
|
||||
@@ -85,8 +85,9 @@ export function Inbox() {
|
||||
|
||||
const approveMutation = useMutation({
|
||||
mutationFn: (id: string) => approvalsApi.approve(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!, "pending") });
|
||||
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");
|
||||
@@ -96,7 +97,7 @@ export function Inbox() {
|
||||
const rejectMutation = useMutation({
|
||||
mutationFn: (id: string) => approvalsApi.reject(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!, "pending") });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.approvals.list(selectedCompanyId!) });
|
||||
},
|
||||
onError: (err) => {
|
||||
setActionError(err instanceof Error ? err.message : "Failed to reject");
|
||||
@@ -107,13 +108,16 @@ export function Inbox() {
|
||||
return <EmptyState icon={InboxIcon} message="Select a company to view inbox." />;
|
||||
}
|
||||
|
||||
const hasApprovals = approvals && approvals.length > 0;
|
||||
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 = hasApprovals || hasAlerts || hasStale;
|
||||
const hasContent = hasActionableApprovals || hasAlerts || hasStale;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -126,7 +130,7 @@ export function Inbox() {
|
||||
)}
|
||||
|
||||
{/* Pending Approvals */}
|
||||
{hasApprovals && (
|
||||
{hasActionableApprovals && (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
||||
@@ -140,7 +144,7 @@ export function Inbox() {
|
||||
</button>
|
||||
</div>
|
||||
<div className="border border-border divide-y divide-border">
|
||||
{approvals!.map((approval) => (
|
||||
{actionableApprovals.map((approval) => (
|
||||
<div key={approval.id} className="p-4 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="h-4 w-4 text-yellow-500 shrink-0" />
|
||||
@@ -185,7 +189,7 @@ export function Inbox() {
|
||||
{/* Alerts */}
|
||||
{hasAlerts && (
|
||||
<>
|
||||
{hasApprovals && <Separator />}
|
||||
{hasActionableApprovals && <Separator />}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
Alerts
|
||||
@@ -226,7 +230,7 @@ export function Inbox() {
|
||||
{/* Stale Work */}
|
||||
{hasStale && (
|
||||
<>
|
||||
{(hasApprovals || hasAlerts) && <Separator />}
|
||||
{(hasActionableApprovals || hasAlerts) && <Separator />}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
Stale Work
|
||||
|
||||
Reference in New Issue
Block a user