# Dashboard 3

> Customer support dashboard with conversation charts, CSAT scores, on-duty team, and recent tickets.

A shadcn/ui block from the [@efferd](https://efferd.com/) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/efferd/dashboard-3).

- Page, with a live preview: https://shadcn.exchange/r/efferd/dashboard-3
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/efferd/dashboard-3-light-17d637b71bf3d4c9.png
- Registry item: https://efferd.com/r/dashboard-3.json
- Categories: dashboard
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://efferd.com/r/dashboard-3.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `avatar`, `badge`, `button`, `card`, `chart`, `dropdown-menu`, `select`, `table`, `@efferd/indicator`, `@efferd/app-shell-3`, `@efferd/formater`

## Files

### `registry/blocks/dashboard/3/channel-breakdown-chart.tsx`

```tsx
"use client";

import { cn } from "@/lib/utils";
import type { ComponentProps } from "react";
import { LabelList, Pie, PieChart } from "recharts";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import {
	type ChartConfig,
	ChartContainer,
	ChartLegend,
	ChartLegendContent,
} from "@/components/ui/chart";
import { Delta, DeltaIcon, DeltaValue } from "@/components/delta";

type ChannelKey = "direct" | "email" | "social";

type ChannelDatum = {
	channel: ChannelKey;
	share: number;
	fill: string;
};

const chartData: ChannelDatum[] = [
	{ channel: "direct", share: 44, fill: "var(--color-direct)" },
	{ channel: "email", share: 36, fill: "var(--color-email)" },
	{ channel: "social", share: 20, fill: "var(--color-social)" },
];

const chartConfig = {
	share: {
		label: "Share",
	},
	direct: {
		label: "Direct",
		color: "var(--chart-1)",
	},
	email: {
		label: "Email",
		color: "var(--chart-3)",
	},
	social: {
		label: "Social",
		color: "var(--chart-5)",
	},
} satisfies ChartConfig;

export function ChannelBreakdownChart({
	className,
	...props
}: ComponentProps<typeof Card>) {
	return (
		<Card
			className={cn("flex flex-col shadow-none dark:ring-0", className)}
			{...props}
		>
			<CardHeader className="items-center space-y-1 pb-0 sm:items-start">
				<div className="flex flex-wrap items-center justify-center gap-2 sm:justify-start">
					<CardTitle >Traffic by channel</CardTitle>
					<Delta value={2.4} variant="badge">
						<DeltaIcon variant="trend" />
						<DeltaValue suffix="pp" />
					</Delta>
				</div>
				<CardDescription>
					Share of new conversations in last 7 days
				</CardDescription>
			</CardHeader>
			<CardContent className="my-auto">
				<ChartContainer
					className="mx-auto aspect-square max-h-72 w-full"
					config={chartConfig}
				>
					<PieChart accessibilityLayer>
						<Pie
							cornerRadius={8}
							data={chartData}
							dataKey="share"
							innerRadius={36}
							nameKey="channel"
							outerRadius="88%"
							stroke="var(--card)"
							strokeWidth={4}
						>
							<LabelList
								className="fill-background font-medium"
								dataKey="share"
								fill="currentColor"
								fontWeight={500}
								formatter={(label) => {
									const n = Number(label);
									return Number.isFinite(n) ? `${n}%` : String(label ?? "");
								}}
								position="inside"
								stroke="none"
							/>
						</Pie>
						<ChartLegend content={<ChartLegendContent nameKey="channel" />} />
					</PieChart>
				</ChartContainer>
			</CardContent>
		</Card>
	);
}
```

### `registry/blocks/dashboard/3/conversation-volume-chart.tsx`

```tsx
"use client";

import { cn } from "@/lib/utils";
import { type ComponentProps, useId, useMemo, useState } from "react";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import {
	formatChartAxisTick,
	formatChartTooltipDate,
	parseIsoCalendarDate,
} from "@/lib/formater";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import {
	type ChartConfig,
	ChartContainer,
	ChartTooltip,
	ChartTooltipContent,
} from "@/components/ui/chart";
import {
	Select,
	SelectContent,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";
import { Delta, DeltaIcon, DeltaValue } from "@/components/delta";

type PeriodDays = 7 | 30 | 60;

type VolumeRow = {
	date: string;
	conversations: number;
};

/**
 * Demo data.
 */
const chartData: VolumeRow[] = [
	{ date: "2026-02-18", conversations: 162 },
	{ date: "2026-02-19", conversations: 162 },
	{ date: "2026-02-20", conversations: 162 },
	{ date: "2026-02-21", conversations: 162 },
	{ date: "2026-02-22", conversations: 162 },
	{ date: "2026-02-23", conversations: 162 },
	{ date: "2026-02-24", conversations: 161 },
	{ date: "2026-02-25", conversations: 161 },
	{ date: "2026-02-26", conversations: 160 },
	{ date: "2026-02-27", conversations: 160 },
	{ date: "2026-02-28", conversations: 159 },
	{ date: "2026-03-01", conversations: 158 },
	{ date: "2026-03-02", conversations: 158 },
	{ date: "2026-03-03", conversations: 157 },
	{ date: "2026-03-04", conversations: 157 },
	{ date: "2026-03-05", conversations: 157 },
	{ date: "2026-03-06", conversations: 157 },
	{ date: "2026-03-07", conversations: 157 },
	{ date: "2026-03-08", conversations: 157 },
	{ date: "2026-03-09", conversations: 157 },
	{ date: "2026-03-10", conversations: 157 },
	{ date: "2026-03-11", conversations: 157 },
	{ date: "2026-03-12", conversations: 157 },
	{ date: "2026-03-13", conversations: 157 },
	{ date: "2026-03-14", conversations: 156 },
	{ date: "2026-03-15", conversations: 156 },
	{ date: "2026-03-16", conversations: 155 },
	{ date: "2026-03-17", conversations: 155 },
	{ date: "2026-03-18", conversations: 154 },
	{ date: "2026-03-19", conversations: 153 },
	{ date: "2026-03-20", conversations: 152 },
	{ date: "2026-03-21", conversations: 151 },
	{ date: "2026-03-22", conversations: 150 },
	{ date: "2026-03-23", conversations: 149 },
	{ date: "2026-03-24", conversations: 149 },
	{ date: "2026-03-25", conversations: 150 },
	{ date: "2026-03-26", conversations: 147 },
	{ date: "2026-03-27", conversations: 151 },
	{ date: "2026-03-28", conversations: 147 },
	{ date: "2026-03-29", conversations: 145 },
	{ date: "2026-03-30", conversations: 143 },
	{ date: "2026-03-31", conversations: 141 },
	{ date: "2026-04-01", conversations: 139 },
	{ date: "2026-04-02", conversations: 137 },
	{ date: "2026-04-03", conversations: 137 },
	{ date: "2026-04-04", conversations: 137 },
	{ date: "2026-04-05", conversations: 137 },
	{ date: "2026-04-06", conversations: 137 },
	{ date: "2026-04-07", conversations: 137 },
	{ date: "2026-04-08", conversations: 136 },
	{ date: "2026-04-09", conversations: 134 },
	{ date: "2026-04-10", conversations: 132 },
	{ date: "2026-04-11", conversations: 129 },
	{ date: "2026-04-12", conversations: 127 },
	{ date: "2026-04-13", conversations: 125 },
	{ date: "2026-04-14", conversations: 124 },
	{ date: "2026-04-15", conversations: 124 },
	{ date: "2026-04-16", conversations: 124 },
	{ date: "2026-04-17", conversations: 124 },
	{ date: "2026-04-18", conversations: 124 },
	{ date: "2026-04-19", conversations: 124 },
	{ date: "2026-04-20", conversations: 122 },
	{ date: "2026-04-21", conversations: 120 },
	{ date: "2026-04-22", conversations: 118 },
	{ date: "2026-04-23", conversations: 115 },
	{ date: "2026-04-24", conversations: 113 },
	{ date: "2026-04-25", conversations: 112 },
	{ date: "2026-04-26", conversations: 111 },
	{ date: "2026-04-27", conversations: 114 },
	{ date: "2026-04-28", conversations: 110 },
	{ date: "2026-04-29", conversations: 113 },
	{ date: "2026-04-30", conversations: 111 },
	{ date: "2026-05-01", conversations: 111 },
	{ date: "2026-05-02", conversations: 107 },
	{ date: "2026-05-03", conversations: 109 },
	{ date: "2026-05-04", conversations: 105 },
	{ date: "2026-05-05", conversations: 103 },
	{ date: "2026-05-06", conversations: 101 },
	{ date: "2026-05-07", conversations: 98 },
	{ date: "2026-05-08", conversations: 100 },
	{ date: "2026-05-09", conversations: 97 },
	{ date: "2026-05-10", conversations: 101 },
	{ date: "2026-05-11", conversations: 99 },
	{ date: "2026-05-12", conversations: 100 },
	{ date: "2026-05-13", conversations: 94 },
	{ date: "2026-05-14", conversations: 102 },
	{ date: "2026-05-15", conversations: 99 },
	{ date: "2026-05-16", conversations: 107 },
	{ date: "2026-05-17", conversations: 104 },
	{ date: "2026-05-18", conversations: 118 },
	{ date: "2026-05-19", conversations: 127 },
];

const lastChartRow = chartData.at(-1);
if (lastChartRow === undefined) {
	throw new Error(
		"ConversationVolumeChart: chartData must include at least one row"
	);
}
const volumeChartReferenceDate = parseIsoCalendarDate(lastChartRow.date);

const chartConfig = {
	conversations: {
		label: "New threads",
		color: "var(--chart-2)",
	},
} satisfies ChartConfig;

export function ConversationVolumeChart({
	className,
	...props
}: ComponentProps<typeof Card>) {
	const chartUid = useId().replace(/:/g, "");
	const idAreaGradient = `conversation-volume-area-grad-${chartUid}`;

	const [periodDays, setPeriodDays] = useState<PeriodDays>(30);

	const chartRows = useMemo(() => {
		const startDate = new Date(volumeChartReferenceDate);
		startDate.setDate(startDate.getDate() - periodDays);
		return chartData.filter(
			(item) => parseIsoCalendarDate(item.date) >= startDate
		);
	}, [periodDays]);

	const growthPctNum = useMemo(() => {
		const first = chartRows[0];
		if (!first) {
			return 0;
		}
		const last = chartRows.at(-1);
		if (!last) {
			return 0;
		}
		const a = first.conversations;
		const b = last.conversations;
		if (!a) {
			return 0;
		}
		return ((b - a) / a) * 100;
	}, [chartRows]);

	let xAxisMinTickGap: number | undefined;
	if (periodDays <= 7) {
		xAxisMinTickGap = undefined;
	} else if (periodDays >= 60) {
		xAxisMinTickGap = 20;
	} else {
		xAxisMinTickGap = 28;
	}

	return (
		<Card
			className={cn(
				"shadow-none md:col-span-2 lg:col-span-3 dark:ring-0",
				className
			)}
			{...props}
		>
			<CardHeader className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
				<div className="min-w-0 space-y-2">
					<div className="flex flex-wrap items-center gap-2">
						<CardTitle >Conversation volume</CardTitle>
						<Delta value={growthPctNum} variant="badge">
							<DeltaIcon variant="trend" />
							<DeltaValue />
						</Delta>
					</div>
					<CardDescription>
						New threads per day for the selected window.
					</CardDescription>
				</div>
				<Select
					onValueChange={(v) => {
						const n = Number(v);
						setPeriodDays(n as PeriodDays);
					}}
					value={String(periodDays)}
				>
					<SelectTrigger
						aria-label="Conversation volume time range"
						className="w-full min-w-36 sm:w-fit"
						size="sm"
					>
						<SelectValue placeholder="Range" />
					</SelectTrigger>
					<SelectContent align="end">
						<SelectItem value="7">Last 7 days</SelectItem>
						<SelectItem value="30">Last 30 days</SelectItem>
						<SelectItem value="60">Last 60 days</SelectItem>
					</SelectContent>
				</Select>
			</CardHeader>
			<CardContent>
				<ChartContainer className="aspect-22/8 w-full" config={chartConfig}>
					<AreaChart
						accessibilityLayer
						data={chartRows}
						margin={{ left: 4, right: 8, top: 8, bottom: 0 }}
					>
						<defs>
							<linearGradient id={idAreaGradient} x1="0" x2="0" y1="0" y2="1">
								<stop
									offset="0%"
									stopColor="var(--color-conversations)"
									stopOpacity={0.45}
								/>
								<stop
									offset="55%"
									stopColor="var(--color-conversations)"
									stopOpacity={0.12}
								/>
								<stop
									offset="100%"
									stopColor="var(--color-conversations)"
									stopOpacity={0}
								/>
							</linearGradient>
						</defs>
						<CartesianGrid className="stroke-border" vertical={false} />
						<XAxis
							axisLine={false}
							dataKey="date"
							interval={periodDays <= 7 ? 0 : "preserveStartEnd"}
							minTickGap={xAxisMinTickGap}
							tickFormatter={(value) =>
								formatChartAxisTick(String(value), periodDays)
							}
							tickLine={false}
							tickMargin={8}
						/>
						<YAxis
							axisLine={false}
							tick={{ className: "tabular-nums" }}
							tickLine={false}
							tickMargin={8}
							width={36}
						/>
						<ChartTooltip
							content={
								<ChartTooltipContent
									className="min-w-34"
									indicator="line"
									labelFormatter={(_, payload) => {
										const row = payload?.[0]?.payload as VolumeRow | undefined;
										if (!row?.date) {
											return "";
										}
										return formatChartTooltipDate(row.date, "long");
									}}
								/>
							}
							cursor={false}
						/>
						<Area
							dataKey="conversations"
							dot={false}
							fill={`url(#${idAreaGradient})`}
							stroke="var(--color-conversations)"
							strokeWidth={2}
							type="natural"
						/>
					</AreaChart>
				</ChartContainer>
			</CardContent>
		</Card>
	);
}
```

### `registry/blocks/dashboard/3/csat-responses-chart.tsx`

```tsx
"use client";

import { cn } from "@/lib/utils";
import type { ComponentProps } from "react";
import { Bar, BarChart, Rectangle, XAxis } from "recharts";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import {
	type ChartConfig,
	ChartContainer,
	ChartTooltip,
	ChartTooltipContent,
} from "@/components/ui/chart";

const chartData = [
	{ day: "Apr 8", direct: 11, email: 9, social: 4 },
	{ day: "Apr 9", direct: 15, email: 11, social: 5 },
	{ day: "Apr 10", direct: 13, email: 10, social: 5 },
	{ day: "Apr 11", direct: 16, email: 12, social: 5 },
	{ day: "Apr 12", direct: 12, email: 10, social: 5 },
	{ day: "Apr 13", direct: 14, email: 10, social: 6 },
	{ day: "Apr 14", direct: 11, email: 9, social: 5 },
	{ day: "Apr 15", direct: 16, email: 7, social: 4 },
	{ day: "Apr 16", direct: 13, email: 11, social: 5 },
	{ day: "Apr 17", direct: 15, email: 11, social: 6 },
] as const;

const chartConfig = {
	direct: {
		label: "Direct",
		color: "var(--chart-1)",
	},
	email: {
		label: "Email",
		color: "var(--chart-3)",
	},
	social: {
		label: "Social",
		color: "var(--chart-5)",
	},
} satisfies ChartConfig;

/** Half of bar width (8) so ends read as fully rounded “caps”. */
const BAR_RADIUS = 5;

/**
 *  column hover background.
 */
function ColumnHoverCursor(props: React.ComponentProps<typeof Rectangle>) {
	return (
		<Rectangle
			fill="var(--muted)"
			fillOpacity={0.5}
			radius={BAR_RADIUS * 2}
			stroke="none"
			{...props}
		/>
	);
}

export function CsatResponsesChart({
	className,
	...props
}: ComponentProps<typeof Card>) {
	return (
		<Card
			className={cn("shadow-none md:col-span-2 dark:ring-0", className)}
			{...props}
		>
			<CardHeader>
				<CardTitle >CSAT responses</CardTitle>
				<CardDescription>
					Post-resolution surveys submitted per day by channel, last 10 days.
				</CardDescription>
			</CardHeader>
			<CardContent>
				<ChartContainer className="aspect-video w-full" config={chartConfig}>
					<BarChart accessibilityLayer data={[...chartData]}>
						<XAxis
							axisLine={false}
							dataKey="day"
							interval={0}
							minTickGap={8}
							tickFormatter={(value) => String(value)}
							tickLine={false}
							tickMargin={10}
						/>
						<ChartTooltip
							content={<ChartTooltipContent hideLabel />}
							cursor={<ColumnHoverCursor />}
						/>
						<Bar
							background={{
								fill: "var(--muted)",
								radius: BAR_RADIUS,
							}}
							barSize={8}
							dataKey="social"
							fill="var(--color-social)"
							overflow="visible"
							radius={[0, 0, BAR_RADIUS, BAR_RADIUS]}
							stackId="csat"
						/>
						<Bar
							barSize={8}
							dataKey="email"
							fill="var(--color-email)"
							overflow="visible"
							radius={0}
							stackId="csat"
						/>
						<Bar
							barSize={8}
							dataKey="direct"
							fill="var(--color-direct)"
							overflow="visible"
							radius={[BAR_RADIUS, BAR_RADIUS, 0, 0]}
							stackId="csat"
						/>
					</BarChart>
				</ChartContainer>
			</CardContent>
		</Card>
	);
}
```

### `registry/blocks/dashboard/3/dashboard.tsx`

```tsx
import { ChannelBreakdownChart } from "@/components/channel-breakdown-chart";
import { ConversationVolumeChart } from "@/components/conversation-volume-chart";
import { CsatResponsesChart } from "@/components/csat-responses-chart";
import { FirstReplyTimeChart } from "@/components/first-reply-time-chart";
import { RecentConversations } from "@/components/recent-conversations";
import { DashboardStats } from "@/components/stats";
import { SupportActivity } from "@/components/support-activity";
import { TeamOnDuty } from "@/components/team-on-duty";

export function Dashboard() {
	return (
		<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
			<DashboardStats />
			<ConversationVolumeChart />
			<ChannelBreakdownChart />
			<CsatResponsesChart />
			<FirstReplyTimeChart />
			<TeamOnDuty />
			<RecentConversations />
			<SupportActivity />
		</div>
	);
}
```

### `registry/blocks/dashboard/3/first-reply-time-chart.tsx`

```tsx
"use client";

import { cn } from "@/lib/utils";
import type { ComponentProps } from "react";
import { CartesianGrid, LabelList, Line, LineChart, XAxis } from "recharts";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import {
	type ChartConfig,
	ChartContainer,
	ChartTooltip,
	ChartTooltipContent,
} from "@/components/ui/chart";
import { Delta, DeltaIcon, DeltaValue } from "@/components/delta";

type ReplyRow = {
	day: string;
	minutes: number;
};

const chartRows: ReplyRow[] = [
	{ day: "Mon", minutes: 5.4 },
	{ day: "Tue", minutes: 4.9 },
	{ day: "Wed", minutes: 5.2 },
	{ day: "Thu", minutes: 4.7 },
	{ day: "Fri", minutes: 4.5 },
	{ day: "Sat", minutes: 5.0 },
	{ day: "Sun", minutes: 4.1 },
];

const firstMinutes = chartRows[0]?.minutes ?? 0;
const lastMinutes = chartRows.at(-1)?.minutes ?? firstMinutes;

/** Positive when median first reply faster Mon → Sun. */
const replyImprovementPct =
	firstMinutes > 0 ? ((firstMinutes - lastMinutes) / firstMinutes) * 100 : 0;

const chartConfig = {
	minutes: {
		label: "Minutes",
		color: "var(--chart-2)",
	},
} satisfies ChartConfig;

export function FirstReplyTimeChart({
	className,
	...props
}: ComponentProps<typeof Card>) {
	return (
		<Card
			className={cn("shadow-none md:col-span-2 dark:ring-0", className)}
			{...props}
		>
			<CardHeader className="space-y-1">
				<div className="flex flex-wrap items-center gap-2">
					<CardTitle >Median first reply</CardTitle>
					<Delta value={replyImprovementPct} variant="badge">
						<DeltaIcon variant="trend" />
						<DeltaValue />
					</Delta>
				</div>
				<CardDescription>
					Minutes to first agent message, last 7 days.
				</CardDescription>
			</CardHeader>
			<CardContent>
				<ChartContainer className="aspect-video w-full" config={chartConfig}>
					<LineChart
						accessibilityLayer
						data={chartRows}
						margin={{ top: 24, left: 20, right: 12, bottom: 8 }}
					>
						<CartesianGrid className="stroke-border" vertical={false} />
						<XAxis
							axisLine={false}
							dataKey="day"
							interval={0}
							tickFormatter={(value) => String(value).slice(0, 3)}
							tickLine={false}
							tickMargin={8}
						/>
						<ChartTooltip
							content={<ChartTooltipContent indicator="line" />}
							cursor={false}
						/>
						<Line
							activeDot={{ r: 6 }}
							dataKey="minutes"
							dot={{ fill: "var(--color-minutes)" }}
							stroke="var(--color-minutes)"
							strokeWidth={2}
							type="natural"
						>
							<LabelList
								className="fill-foreground"
								dataKey="minutes"
								fontSize={12}
								formatter={(label) => {
									const n = Number(label);
									return Number.isFinite(n)
										? `${n.toFixed(1)}m`
										: String(label ?? "");
								}}
								offset={12}
								position="top"
							/>
						</Line>
					</LineChart>
				</ChartContainer>
			</CardContent>
		</Card>
	);
}
```

### `registry/blocks/dashboard/3/recent-conversations.tsx`

```tsx
import { cn } from "@/lib/utils";
import type { ComponentProps } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import {
	Table,
	TableBody,
	TableCell,
	TableHead,
	TableHeader,
	TableRow,
} from "@/components/ui/table";

type Conversation = {
	customer: string;
	subject: string;
	channel: "email" | "chat";
	waitMinutes: number;
	state: "waiting" | "open" | "snoozed";
};

const rows: Conversation[] = [
	{
		customer: "Northwind Labs",
		subject: "Billing portal access",
		channel: "email",
		waitMinutes: 6,
		state: "waiting",
	},
	{
		customer: "Blue River Co.",
		subject: "Shipment ETA",
		channel: "chat",
		waitMinutes: 1,
		state: "open",
	},
	{
		customer: "Oak Street Studio",
		subject: "API rate limits",
		channel: "email",
		waitMinutes: 58,
		state: "snoozed",
	},
	{
		customer: "Harbor Freight LLC",
		subject: "Workspace SSO",
		channel: "chat",
		waitMinutes: 3,
		state: "open",
	},
];

function formatWaitTime(minutes: number): string {
	if (minutes <= 0) {
		return "Just now";
	}
	if (minutes === 1) {
		return "1 minute";
	}
	if (minutes < 55) {
		return `${minutes} minutes`;
	}
	if (minutes < 60) {
		return "Almost an hour";
	}
	if (minutes < 75) {
		return "About an hour";
	}
	if (minutes < 120) {
		return "Over an hour";
	}
	const hours = Math.round(minutes / 60);
	return hours === 1 ? "About an hour" : `About ${hours} hours`;
}

function statusVariant(
	state: Conversation["state"]
): ComponentProps<typeof Badge>["variant"] {
	if (state === "waiting") {
		return "destructive";
	}
	if (state === "snoozed") {
		return "outline";
	}
	return "secondary";
}

function statusLabel(state: Conversation["state"]): string {
	if (state === "waiting") {
		return "In queue";
	}
	if (state === "snoozed") {
		return "Snoozed";
	}
	return "Active";
}

function channelIcon(channel: Conversation["channel"]) {
	if (channel === "email") {
		return (
			<IconPlaceholder
				className="size-3.5 shrink-0"
				hugeicons="MailSend01Icon"
				lucide="MailIcon"
				phosphor="EnvelopeIcon"
				remixicon="RiMailLine"
				tabler="IconMail"
			/>
		);
	}
	return (
		<IconPlaceholder
			className="size-3.5 shrink-0"
			hugeicons="Chat01Icon"
			lucide="MessageCircleIcon"
			phosphor="ChatIcon"
			remixicon="RiChat1Line"
			tabler="IconMessageCircle"
		/>
	);
}

export function RecentConversations({
	className,
	...props
}: ComponentProps<typeof Card>) {
	return (
		<Card
			className={cn("gap-0 shadow-none md:col-span-2 dark:ring-0", className)}
			{...props}
		>
			<CardHeader className="border-b">
				<CardTitle >Recent conversations</CardTitle>
				<CardDescription>Latest 4 threads from your inbox</CardDescription>
			</CardHeader>
			<CardContent className="p-0">
				<Table>
					<TableHeader>
						<TableRow className="hover:bg-transparent">
							<TableHead className="pl-6">Customer</TableHead>
							<TableHead className="hidden sm:table-cell">Topic</TableHead>
							<TableHead>Channel</TableHead>
							<TableHead className="text-right">Wait</TableHead>
							<TableHead className="pr-6 text-right">Status</TableHead>
						</TableRow>
					</TableHeader>
					<TableBody>
						{rows.map((r) => {
							return (
								<TableRow
									className="h-14 hover:bg-transparent"
									key={`${r.customer}-${r.subject}`}
								>
									<TableCell className="max-w-36 truncate pl-6 font-medium">
										{r.customer}
									</TableCell>
									<TableCell className="hidden max-w-32 sm:table-cell">
										<span className="line-clamp-1 text-muted-foreground text-sm">
											{r.subject}
										</span>
									</TableCell>
									<TableCell>
										<span className="inline-flex items-center gap-2 font-medium text-sm capitalize">
											{channelIcon(r.channel)}
											{r.channel}
										</span>
									</TableCell>
									<TableCell className="text-right text-muted-foreground text-sm">
										{formatWaitTime(r.waitMinutes)}
									</TableCell>
									<TableCell className="pr-6 text-right">
										<Badge variant={statusVariant(r.state)}>
											{statusLabel(r.state)}
										</Badge>
									</TableCell>
								</TableRow>
							);
						})}
					</TableBody>
				</Table>
				<div className="flex justify-center border-t py-3">
					<Button asChild size="sm" variant="ghost">
						<a href="#/inbox">
							View all conversations
							<IconPlaceholder
								aria-hidden="true"
								data-icon="inline-end"
								hugeicons="ArrowRight02Icon"
								lucide="ArrowRightIcon"
								phosphor="ArrowRightIcon"
								remixicon="RiArrowRightLine"
								tabler="IconArrowRight"
							/>
						</a>
					</Button>
				</div>
			</CardContent>
		</Card>
	);
}
```

### `registry/blocks/dashboard/3/stats.tsx`

```tsx
import { cn } from "@/lib/utils";
import {
	Card,
	CardContent,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import { Delta, DeltaIcon, DeltaValue } from "@/components/delta";

type Stat = {
	label: string;
	value: string;
	delta: number;
	footnote: string;
	/** When true, a negative delta is treated as favorable (e.g. queue depth, reply time). */
	lowerIsBetter: boolean;
};

const stats: readonly Stat[] = [
	{
		label: "Open queue",
		value: "38",
		delta: -12.4,
		footnote: "vs yesterday",
		lowerIsBetter: true,
	},
	{
		label: "Active conversations",
		value: "126",
		delta: 5.2,
		footnote: "vs last week",
		lowerIsBetter: false,
	},
	{
		label: "Median first reply",
		value: "4.1m",
		delta: -8.0,
		footnote: "vs last week",
		lowerIsBetter: true,
	},
	{
		label: "CSAT (30d)",
		value: "94%",
		delta: 1.1,
		footnote: "vs prior 30d",
		lowerIsBetter: false,
	},
];

export function DashboardStats() {
	return (
		<>
			{stats.map((s) => (
				<Card className={cn("shadow-none dark:ring-0")} key={s.label}>
					<CardHeader>
						<CardTitle className="font-normal text-muted-foreground text-xs">
							{s.label}
						</CardTitle>
					</CardHeader>
					<CardContent className="flex flex-col gap-2">
						<p className="font-semibold text-2xl tabular-nums">{s.value}</p>
						<div className="flex items-center gap-1 text-xs">
							<Delta value={s.delta}>
								<DeltaIcon />
								<DeltaValue />
							</Delta>
							<span className="text-muted-foreground">{s.footnote}</span>
						</div>
					</CardContent>
				</Card>
			))}
		</>
	);
}
```

### `registry/blocks/dashboard/3/support-activity.tsx`

```tsx
import { cn } from "@/lib/utils";
import type { ComponentProps } from "react";
import { Button } from "@/components/ui/button";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";

const items = [
	{
		title: "SLA warning cleared for Oak Street Studio",
		time: "12 min ago",
		icon: (
			<IconPlaceholder
				hugeicons="ShieldCheck"
				lucide="ShieldCheckIcon"
				phosphor="ShieldCheckIcon"
				remixicon="RiShieldCheckLine"
				tabler="IconShieldCheck"
			/>
		),
	},
	{
		title: "Conversation escalated to Tier 2",
		time: "28 min ago",
		icon: (
			<IconPlaceholder
				hugeicons="Layers01Icon"
				lucide="LayersIcon"
				phosphor="StackIcon"
				remixicon="RiStackLine"
				tabler="IconStack"
			/>
		),
	},
	{
		title: "Macro “Refund approved” updated",
		time: "1 hr ago",
		icon: (
			<IconPlaceholder
				hugeicons="MagicWand01Icon"
				lucide="WandIcon"
				phosphor="MagicWandIcon"
				remixicon="RiMagicLine"
				tabler="IconWand"
			/>
		),
	},
	{
		title: "New customer segment synced from CRM",
		time: "3 hr ago",
		icon: (
			<IconPlaceholder
				hugeicons="UserGroupIcon"
				lucide="UsersRoundIcon"
				phosphor="UsersThreeIcon"
				remixicon="RiGroupLine"
				tabler="IconUsersGroup"
			/>
		),
	},
] as const;

export function SupportActivity({
	className,
	...props
}: ComponentProps<typeof Card>) {
	return (
		<Card className={cn("gap-0 shadow-none dark:ring-0", className)} {...props}>
			<CardHeader className="border-b">
				<CardTitle >Workspace activity</CardTitle>
				<CardDescription>Operational signals.</CardDescription>
			</CardHeader>
			<CardContent className="px-0">
				<ul className="flex flex-col divide-y divide-border">
					{items.map((item) => (
						<li className="flex h-18 items-center gap-3 px-3" key={item.title}>
							<span
								aria-hidden="true"
								className="flex size-10 shrink-0 items-center justify-center [&_svg]:size-4"
							>
								{item.icon}
							</span>
							<div className="min-w-0 flex-1 space-y-1">
								<p className="line-clamp-2 text-pretty text-foreground text-xs leading-snug">
									{item.title}
								</p>
								<p className="text-muted-foreground text-xs tabular-nums">
									{item.time}
								</p>
							</div>
						</li>
					))}
				</ul>
			</CardContent>
			<div className="flex items-center justify-center">
				<Button asChild size="sm" variant="ghost">
					<a href="/#">
						View All
						<IconPlaceholder
							aria-hidden="true"
							data-icon="inline-end"
							hugeicons="ArrowRight02Icon"
							lucide="ArrowRightIcon"
							phosphor="ArrowRightIcon"
							remixicon="RiArrowRightLine"
							tabler="IconArrowRight"
						/>
					</a>
				</Button>
			</div>
		</Card>
	);
}
```

### `registry/blocks/dashboard/3/team-on-duty.tsx`

```tsx
"use client";

import { cn } from "@/lib/utils";
import { type ComponentProps, useState } from "react";
import {
	Avatar,
	AvatarFallback,
	AvatarImage,
} from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
	Card,
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import {
	DropdownMenu,
	DropdownMenuContent,
	DropdownMenuItem,
	DropdownMenuLabel,
	DropdownMenuSeparator,
	DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { StatusIndicator } from "@/components/indicator";

type Teammate = {
	id: string;
	name: string;
	status: "Online" | "Away";
	/** Conversations currently assigned to this teammate */
	open: number;
	image?: string;
};

const INITIAL_TEAMMATES: readonly Teammate[] = [
	{
		id: "amelia",
		name: "Amelia Park",
		status: "Online",
		open: 9,
		image: "https://avatar.vercel.sh/ameliapark",
	},
	{
		id: "noah",
		name: "Noah Ibarra",
		status: "Online",
		open: 7,
		image: "https://avatar.vercel.sh/noahi",
	},
	{
		id: "priya",
		name: "Priya Desai",
		status: "Away",
		open: 4,
		image: "https://avatar.vercel.sh/priyadesai",
	},
	{
		id: "marcus",
		name: "Marcus Chen",
		status: "Online",
		open: 11,
		image: "https://avatar.vercel.sh/marcuschen",
	},
	{
		id: "emily",
		name: "Emily Johnson",
		status: "Away",
		open: 2,
		image: "https://avatar.vercel.sh/emilyjohnson",
	},
];

function getInitials(name: string) {
	return name
		.split(" ")
		.map((word) => word.charAt(0).toUpperCase())
		.join("");
}

export function TeamOnDuty({
	className,
	...props
}: ComponentProps<typeof Card>) {
	const [teammates, setTeammates] = useState<Teammate[]>(() => [
		...INITIAL_TEAMMATES,
	]);

	function pullNextConversation(id: string) {
		setTeammates((prev) =>
			prev.map((t) =>
				t.id === id ? { ...t, open: Math.max(0, t.open - 1) } : t
			)
		);
	}

	return (
		<Card className={cn("shadow-none dark:ring-0", className)} {...props}>
			<CardHeader className="border-b">
				<CardTitle >Team on duty</CardTitle>
				<CardDescription>Who is carrying the queue right now</CardDescription>
			</CardHeader>
			<CardContent className="p-0">
				<ul className="flex flex-col divide-y divide-border">
					{teammates.map((t) => (
						<li
							className="flex items-center gap-2 p-3 first:pt-0 last:pb-0 sm:gap-3"
							key={t.id}
						>
							<Avatar className="size-8">
								<AvatarImage alt={t.name} src={t.image} />
								<AvatarFallback>{getInitials(t.name)}</AvatarFallback>
							</Avatar>
							<div className="min-w-0 flex-1 pr-1">
								<p className="truncate font-medium text-foreground text-sm leading-snug">
									{t.name}
								</p>
								<p className="flex items-center gap-2 text-[10px] leading-snug">
									<span className="flex shrink-0 items-center gap-1">
										<StatusIndicator
											color={t.status === "Online" ? "emerald" : "amber"}
											pulse={t.status === "Online"}
										/>
										{t.status}
									</span>
									<span className="inline-flex size-1 rounded-full bg-foreground/80" />
									<span className="tabular-nums">{t.open} assigned</span>
								</p>
							</div>
							<DropdownMenu>
								<DropdownMenuTrigger asChild>
									<Button
										aria-label={`Actions for ${t.name}`}
										size="icon-xs"
										variant="ghost"
									>
										<IconPlaceholder
											hugeicons="MoreHorizontalCircle01Icon"
											lucide="EllipsisIcon"
											phosphor="DotsThreeIcon"
											remixicon="RiMoreLine"
											tabler="IconDots"
										/>
									</Button>
								</DropdownMenuTrigger>
								<DropdownMenuContent align="end" className="min-w-52">
									<DropdownMenuLabel className="font-normal text-muted-foreground text-xs">
										{t.name}
									</DropdownMenuLabel>
									<DropdownMenuSeparator />
									<DropdownMenuItem className="gap-2">
										<IconPlaceholder
											className="size-4 opacity-70"
											hugeicons="MailSend01Icon"
											lucide="SendIcon"
											phosphor="PaperPlaneTiltIcon"
											remixicon="RiSendPlaneLine"
											tabler="IconSend"
										/>
										Message
									</DropdownMenuItem>
									<DropdownMenuItem
										className="gap-2"
										disabled={t.open === 0}
										onSelect={() => {
											pullNextConversation(t.id);
										}}
									>
										<IconPlaceholder
											className="size-4 opacity-70"
											hugeicons="CheckListIcon"
											lucide="ListChecksIcon"
											phosphor="ListChecksIcon"
											remixicon="RiListCheck2"
											tabler="IconChecklist"
										/>
										Pull next conversation
									</DropdownMenuItem>
								</DropdownMenuContent>
							</DropdownMenu>
						</li>
					))}
				</ul>
			</CardContent>
		</Card>
	);
}
```

### `registry/blocks/dashboard/delta.tsx`

```tsx
"use client";
import { cn } from "@/lib/utils";
import * as React from "react";
import { Badge } from "@/components/ui/badge";

type DeltaIconVariant = "default" | "trend" | "arrow";
type DeltaVariant = "default" | "badge";

type DeltaContextValue = {
	value: number;
};

const DeltaContext = React.createContext<DeltaContextValue | null>(null);

function useDeltaValue() {
	const context = React.useContext(DeltaContext);

	if (!context) {
		throw new Error(
			"DeltaIcon and DeltaValue must be used inside a `Delta` component."
		);
	}

	return context.value;
}

function Delta({
	className,
	value,
	variant = "default",
	...props
}: React.ComponentProps<"div"> & {
	value: number;
	variant?: DeltaVariant;
}) {
	return (
		<DeltaContext.Provider value={{ value }}>
			{variant === "badge" ? (
				<Badge
					className={cn(
						"gap-1 border-none tabular-nums [&_svg]:size-4 [&_svg]:shrink-0",
						value > 0
							? "bg-emerald-500/10 text-emerald-500"
							: "bg-red-500/10 text-red-500",
						className
					)}
					data-slot="delta"
					variant="secondary"
					{...(props as React.ComponentProps<typeof Badge>)}
				/>
			) : (
				<div
					className={cn(
						"inline-flex items-center gap-1 text-muted-foreground tabular-nums",
						"[&_svg]:size-3 [&_svg]:shrink-0",
						value > 0 ? "text-emerald-600 dark:text-emerald-400" : "",
						value < 0 ? "text-rose-600 dark:text-rose-400" : "",
						className
					)}
					data-slot="delta"
					{...props}
				/>
			)}
		</DeltaContext.Provider>
	);
}

function FilledShell({
	value,
	children,
}: {
	value: number;
	children: React.ReactNode;
}) {
	return (
		<span
			className={cn(
				"inline-flex size-3 shrink-0 items-center justify-center rounded-full",
				"[&_svg]:size-2! [&_svg]:shrink-0 [&_svg]:stroke-3! [&_svg]:text-background",
				value > 0 && "bg-emerald-500",
				value < 0 && "bg-red-500",
				(!value || value === 0) && "bg-muted-foreground"
			)}
			data-slot="delta-icon"
		>
			{children}
		</span>
	);
}

function DeltaIcon({
	variant = "default",
	filled = false,
	className,
	...props
}: Omit<React.ComponentProps<"svg">, "fill"> & {
	variant?: DeltaIconVariant;
	filled?: boolean;
}) {
	const resolvedValue = useDeltaValue();

	const mergedClassName = cn(className);

	const shell = (node: React.ReactElement) =>
		filled ? <FilledShell value={resolvedValue}>{node}</FilledShell> : node;

	const slotProps = filled ? {} : { "data-slot": "delta-icon" as const };

	if (!resolvedValue || resolvedValue === 0) {
		return shell(
			<IconPlaceholder
				{...slotProps}
				className={mergedClassName}
				hugeicons="MinusSignIcon"
				lucide="MinusIcon"
				phosphor="MinusIcon"
				remixicon="RiSubtractLine"
				tabler="IconMinus"
				{...props}
			/>
		);
	}

	if (resolvedValue > 0) {
		if (variant === "trend") {
			return shell(
				<IconPlaceholder
					{...slotProps}
					className={mergedClassName}
					hugeicons="TradeUpIcon"
					lucide="TrendingUpIcon"
					phosphor="TrendUpIcon"
					remixicon="RiArrowRightUpLine"
					tabler="IconTrendingUp"
					{...props}
				/>
			);
		}

		if (variant === "arrow") {
			return shell(
				<IconPlaceholder
					{...slotProps}
					className={mergedClassName}
					hugeicons="ArrowUp02Icon"
					lucide="ArrowUpIcon"
					phosphor="ArrowUpIcon"
					remixicon="RiArrowUpLine"
					tabler="IconArrowUp"
					{...props}
				/>
			);
		}

		return shell(
			<IconPlaceholder
				{...slotProps}
				className={mergedClassName}
				hugeicons="ArrowUp01Icon"
				lucide="ChevronUpIcon"
				phosphor="CaretUpIcon"
				remixicon="RiArrowUpSLine"
				tabler="IconChevronUp"
				{...props}
			/>
		);
	}

	if (variant === "trend") {
		return shell(
			<IconPlaceholder
				{...slotProps}
				className={mergedClassName}
				hugeicons="TradeDownIcon"
				lucide="TrendingDownIcon"
				phosphor="TrendDownIcon"
				remixicon="RiArrowRightDownLine"
				tabler="IconTrendingDown"
				{...props}
			/>
		);
	}

	if (variant === "arrow") {
		return shell(
			<IconPlaceholder
				{...slotProps}
				className={mergedClassName}
				hugeicons="ArrowDown02Icon"
				lucide="ArrowDownIcon"
				phosphor="ArrowDownIcon"
				remixicon="RiArrowDownLine"
				tabler="IconArrowDown"
				{...props}
			/>
		);
	}

	return shell(
		<IconPlaceholder
			{...slotProps}
			className={mergedClassName}
			hugeicons="ArrowDown01Icon"
			lucide="ChevronDownIcon"
			phosphor="CaretDownIcon"
			remixicon="RiArrowDownSLine"
			tabler="IconChevronDown"
			{...props}
		/>
	);
}

function DeltaValue({
	className,
	precision = 1,
	suffix = "%",
	absolute = true,
	...props
}: React.ComponentProps<"span"> & {
	precision?: number;
	suffix?: string;
	absolute?: boolean;
}) {
	const resolvedValue = useDeltaValue();

	const formattedValue = (
		absolute ? Math.abs(resolvedValue) : resolvedValue
	).toFixed(precision);

	return (
		<span
			className={cn("tabular-nums", className)}
			data-slot="delta-value"
			{...props}
		>
			{formattedValue}
			{suffix}
		</span>
	);
}
export { Delta, DeltaIcon, DeltaValue };
```
