Building an Agent Admin Dashboard: React Components for Monitoring and Configuration
Design and build an admin dashboard for AI agents with metric cards, real-time charts, configuration panels, and activity logs using React, TypeScript, and TanStack Query.
What an Agent Dashboard Needs
An agent admin dashboard serves two audiences: operations teams monitoring agent health and behavior, and product teams configuring agent behavior. The dashboard must display key metrics at a glance, show conversation activity in real-time, surface errors and escalations, and provide configuration controls for agent parameters.
Dashboard Layout
Use a grid-based layout with a sidebar for navigation and a main content area divided into metric cards at the top and detailed panels below.
function AgentDashboard() {
return (
<div className="flex h-screen bg-gray-50">
<Sidebar />
<main className="flex-1 overflow-y-auto p-6">
<h1 className="text-2xl font-bold mb-6">Agent Overview</h1>
<MetricCardsRow />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6">
<ConversationChart />
<RecentActivity />
</div>
<AgentConfigPanel />
</main>
</div>
);
}
Metric Cards with Real-Time Data
Metric cards show the most important numbers: total conversations, average response time, error rate, and user satisfaction score. Fetch these with TanStack Query for automatic background refetching.
import { useQuery } from "@tanstack/react-query";
interface AgentMetrics {
totalConversations: number;
avgResponseTimeMs: number;
errorRate: number;
satisfactionScore: number;
}
function MetricCardsRow() {
const { data, isLoading } = useQuery<AgentMetrics>({
queryKey: ["agent-metrics"],
queryFn: () =>
fetch("/api/admin/metrics").then((r) => r.json()),
refetchInterval: 30_000, // Refresh every 30 seconds
});
if (isLoading) return <MetricCardsSkeleton />;
return (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard
label="Conversations"
value={data!.totalConversations.toLocaleString()}
trend="+12%"
trendUp={true}
/>
<MetricCard
label="Avg Response"
value={`${(data!.avgResponseTimeMs / 1000).toFixed(1)}s`}
trend="-8%"
trendUp={true}
/>
<MetricCard
label="Error Rate"
value={`${(data!.errorRate * 100).toFixed(2)}%`}
trend="+0.3%"
trendUp={false}
/>
<MetricCard
label="Satisfaction"
value={`${data!.satisfactionScore.toFixed(1)}/5`}
trend="+0.2"
trendUp={true}
/>
</div>
);
}
The Metric Card Component
Each card displays a label, value, and trend indicator. The trend arrow and color change based on whether the direction is positive or negative.
interface MetricCardProps {
label: string;
value: string;
trend: string;
trendUp: boolean;
}
function MetricCard({ label, value, trend, trendUp }: MetricCardProps) {
return (
<div className="bg-white rounded-xl border p-5">
<p className="text-sm text-gray-500 mb-1">{label}</p>
<p className="text-2xl font-bold text-gray-900">{value}</p>
<p
className={`text-sm mt-2 ${
trendUp ? "text-green-600" : "text-red-600"
}`}
>
{trendUp ? "^" : "v"} {trend} vs last week
</p>
</div>
);
}
Skeleton Loading States
Dashboard components should show skeleton placeholders during data loading instead of blank space. This prevents layout shift and communicates that data is on the way.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
function MetricCardsSkeleton() {
return (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="bg-white rounded-xl border p-5">
<div className="h-4 w-20 bg-gray-200 rounded animate-pulse mb-2" />
<div className="h-8 w-24 bg-gray-200 rounded animate-pulse mb-2" />
<div className="h-3 w-28 bg-gray-200 rounded animate-pulse" />
</div>
))}
</div>
);
}
Recent Activity Feed
An activity log shows recent conversations, errors, and escalations in chronological order. Use a polling query to keep it up-to-date.
interface ActivityItem {
id: string;
type: "conversation" | "error" | "escalation";
summary: string;
timestamp: string;
}
function RecentActivity() {
const { data } = useQuery<ActivityItem[]>({
queryKey: ["agent-activity"],
queryFn: () =>
fetch("/api/admin/activity?limit=20").then((r) => r.json()),
refetchInterval: 10_000,
});
const typeStyles: Record<ActivityItem["type"], string> = {
conversation: "bg-blue-100 text-blue-700",
error: "bg-red-100 text-red-700",
escalation: "bg-yellow-100 text-yellow-700",
};
return (
<div className="bg-white rounded-xl border p-5">
<h2 className="font-semibold text-lg mb-4">Recent Activity</h2>
<div className="space-y-3 max-h-80 overflow-y-auto">
{data?.map((item) => (
<div key={item.id} className="flex items-start gap-3">
<span
className={`text-xs px-2 py-0.5 rounded-full
font-medium ${typeStyles[item.type]}`}
>
{item.type}
</span>
<div className="flex-1 min-w-0">
<p className="text-sm text-gray-700 truncate">
{item.summary}
</p>
<p className="text-xs text-gray-400">{item.timestamp}</p>
</div>
</div>
))}
</div>
</div>
);
}
Agent Configuration Panel
Allow admins to tweak agent parameters like system prompt, temperature, max tokens, and enabled tools without deploying code.
import { useMutation, useQueryClient } from "@tanstack/react-query";
interface AgentConfig {
systemPrompt: string;
temperature: number;
maxTokens: number;
enabledTools: string[];
}
function AgentConfigPanel() {
const queryClient = useQueryClient();
const { data: config } = useQuery<AgentConfig>({
queryKey: ["agent-config"],
queryFn: () =>
fetch("/api/admin/config").then((r) => r.json()),
});
const mutation = useMutation({
mutationFn: (updated: AgentConfig) =>
fetch("/api/admin/config", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(updated),
}),
onSuccess: () =>
queryClient.invalidateQueries({ queryKey: ["agent-config"] }),
});
if (!config) return null;
return (
<div className="bg-white rounded-xl border p-6 mt-6">
<h2 className="font-semibold text-lg mb-4">
Agent Configuration
</h2>
<label className="block text-sm font-medium mb-1">
System Prompt
</label>
<textarea
defaultValue={config.systemPrompt}
rows={4}
className="w-full border rounded-lg p-3 text-sm mb-4"
/>
<button
onClick={() => mutation.mutate(config)}
className="bg-blue-600 text-white px-4 py-2 rounded-lg
text-sm disabled:opacity-50"
disabled={mutation.isPending}
>
{mutation.isPending ? "Saving..." : "Save Changes"}
</button>
</div>
);
}
FAQ
How do I add charts to the dashboard?
Use a charting library like Recharts or Chart.js with a React wrapper. Fetch time-series data from your API (grouped by hour or day) and pass it to a line or bar chart component. Recharts integrates naturally with React because its charts are composed from React components like <LineChart>, <Line>, and <XAxis>.
Should I use WebSockets or polling for real-time dashboard updates?
Polling with TanStack Query's refetchInterval is simpler and works well for dashboards where 10-30 second latency is acceptable. Use WebSockets only if you need sub-second updates, such as live conversation transcripts or real-time error alerts that require immediate operator attention.
How do I restrict dashboard access to admin users?
Wrap your dashboard routes in an authentication guard that checks the user's role. In Next.js, use middleware to redirect non-admin users before the page loads. On the API side, every admin endpoint should verify the JWT token contains an admin role claim and return 403 if it does not.
#AdminDashboard #React #Monitoring #TypeScript #AIAgentManagement #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.