Skip to content
Learn Agentic AI
Learn Agentic AI12 min read3 views

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.

flowchart LR
    INPUT(["User intent"])
    PARSE["Parse plus<br/>classify"]
    PLAN["Plan and tool<br/>selection"]
    AGENT["Agent loop<br/>LLM plus tools"]
    GUARD{"Guardrails<br/>and policy"}
    EXEC["Execute and<br/>verify result"]
    OBS[("Trace and metrics")]
    OUT(["Outcome plus<br/>next action"])
    INPUT --> PARSE --> PLAN --> AGENT --> GUARD
    GUARD -->|Pass| EXEC --> OUT
    GUARD -->|Fail| AGENT
    AGENT --> OBS
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
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.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
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.

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.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

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

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.