diff --git a/__tests__/admin/SearchAnalyticsPage.test.jsx b/__tests__/admin/SearchAnalyticsPage.test.jsx
new file mode 100644
index 00000000..0b6ef4d9
--- /dev/null
+++ b/__tests__/admin/SearchAnalyticsPage.test.jsx
@@ -0,0 +1,221 @@
+/**
+ * Search analytics page — loading / data / error state tests (#326).
+ * -------------------------------------------------------------------
+ * The page renders query volume charts, CTR trend, top queries table,
+ * and zero-result queries list. Metrics the platform does not instrument
+ * yet must render an explicit "Not tracked yet" placeholder instead of
+ * a silent zero. These tests mock the analytics service and assert the
+ * presentational branching only.
+ */
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen } from "@testing-library/react";
+
+vi.mock("@/lib/config/font.config", () => ({
+ poppins_400: { className: "" },
+ poppins_500: { className: "" },
+ poppins_600: { className: "" },
+}));
+
+const serviceState = vi.hoisted(() => ({ current: null }));
+vi.mock("@/lib/actions/admin-search-analytics", () => ({
+ fetchSearchAnalytics: (...args) => serviceState.current(...args),
+}));
+
+function makeAnalytics(overrides = {}) {
+ return {
+ generatedAt: "2026-08-25T00:00:00.000Z",
+ dateRange: {
+ from: "2026-07-26T00:00:00.000Z",
+ to: "2026-08-25T00:00:00.000Z",
+ },
+ totals: {
+ totalQueries: { label: "Total Queries", value: 0, tracked: false },
+ uniqueQueries: { label: "Unique Queries", value: 0, tracked: false },
+ zeroResultQueries: {
+ label: "Zero-Result Queries",
+ value: 0,
+ tracked: false,
+ },
+ avgClickThroughRate: {
+ label: "Avg. Click-Through Rate",
+ value: null,
+ tracked: false,
+ unit: "%",
+ },
+ },
+ topQueries: [],
+ zeroResultQueries: [],
+ weeklyTrends: [
+ { week: "Week 1", queries: 0, zeroResults: 0, clickThroughRate: null },
+ { week: "Week 2", queries: 0, zeroResults: 0, clickThroughRate: null },
+ { week: "Week 3", queries: 0, zeroResults: 0, clickThroughRate: null },
+ { week: "Week 4", queries: 0, zeroResults: 0, clickThroughRate: null },
+ ],
+ ...overrides,
+ };
+}
+
+let SearchAnalyticsPage;
+beforeEach(async () => {
+ if (!SearchAnalyticsPage) {
+ const mod = await import(
+ "@/app/[locale]/admin/analytics/search/page"
+ );
+ SearchAnalyticsPage = mod.default;
+ }
+});
+
+describe("SearchAnalyticsPage — states", () => {
+ it("renders skeleton placeholders while loading", () => {
+ serviceState.current = () => new Promise(() => {});
+ const { container } = render( );
+ expect(screen.getByText("Search Analytics")).toBeInTheDocument();
+ expect(
+ container.querySelector('[data-slot="skeleton"]')
+ ).not.toBeNull();
+ });
+
+ it("renders summary stat cards when data loads", async () => {
+ serviceState.current = () => Promise.resolve(makeAnalytics());
+ render( );
+
+ expect(
+ await screen.findByText("Search Analytics")
+ ).toBeInTheDocument();
+ expect(screen.getByText("Total Queries")).toBeInTheDocument();
+ expect(screen.getByText("Unique Queries")).toBeInTheDocument();
+ expect(
+ screen.getAllByText("Zero-Result Queries").length
+ ).toBeGreaterThanOrEqual(1);
+ expect(
+ screen.getByText("Avg. Click-Through Rate")
+ ).toBeInTheDocument();
+ });
+
+ it("shows 'Not tracked yet' placeholders for untracked metrics", async () => {
+ serviceState.current = () => Promise.resolve(makeAnalytics());
+ render( );
+
+ await screen.findByText("Search Analytics");
+
+ const placeholders = screen.getAllByText(/not tracked yet/i);
+ // At least the 4 summary stat cards + charts + tables
+ expect(placeholders.length).toBeGreaterThanOrEqual(4);
+ });
+
+ it("renders the top queries and zero-result sections", async () => {
+ serviceState.current = () => Promise.resolve(makeAnalytics());
+ render( );
+
+ await screen.findByText("Search Analytics");
+ expect(screen.getAllByText("Top Queries").length).toBeGreaterThanOrEqual(1);
+ expect(
+ screen.getAllByText("Zero-Result Queries").length
+ ).toBeGreaterThanOrEqual(1);
+ });
+
+ it("renders top queries data when available", async () => {
+ const data = makeAnalytics({
+ topQueries: [
+ {
+ query: "quran tafsir",
+ count: 142,
+ clickThroughRate: 67.5,
+ trend: 12.3,
+ },
+ {
+ query: "arabic grammar",
+ count: 98,
+ clickThroughRate: 45.2,
+ trend: -5.1,
+ },
+ ],
+ totals: {
+ totalQueries: { label: "Total Queries", value: 240, tracked: true },
+ uniqueQueries: { label: "Unique Queries", value: 87, tracked: true },
+ zeroResultQueries: {
+ label: "Zero-Result Queries",
+ value: 12,
+ tracked: true,
+ },
+ avgClickThroughRate: {
+ label: "Avg. Click-Through Rate",
+ value: 56.3,
+ tracked: true,
+ unit: "%",
+ },
+ },
+ });
+ serviceState.current = () => Promise.resolve(data);
+ render( );
+
+ await screen.findByText("quran tafsir");
+ expect(screen.getByText("arabic grammar")).toBeInTheDocument();
+ expect(screen.getByText("142")).toBeInTheDocument();
+ expect(screen.getByText("98")).toBeInTheDocument();
+ expect(screen.getByText("67.5%")).toBeInTheDocument();
+ expect(screen.getByText("45.2%")).toBeInTheDocument();
+ expect(screen.getByText("240")).toBeInTheDocument();
+ expect(screen.getByText("87")).toBeInTheDocument();
+ expect(screen.getByText("56.3%")).toBeInTheDocument();
+ });
+
+ it("renders zero-result queries data when available", async () => {
+ const data = makeAnalytics({
+ zeroResultQueries: [
+ {
+ query: "advanced nahw exercises",
+ count: 8,
+ lastSearched: "2026-08-20T10:00:00.000Z",
+ },
+ ],
+ totals: {
+ totalQueries: { label: "Total Queries", value: 240, tracked: true },
+ uniqueQueries: { label: "Unique Queries", value: 87, tracked: true },
+ zeroResultQueries: {
+ label: "Zero-Result Queries",
+ value: 12,
+ tracked: true,
+ },
+ avgClickThroughRate: {
+ label: "Avg. Click-Through Rate",
+ value: 56.3,
+ tracked: true,
+ unit: "%",
+ },
+ },
+ });
+ serviceState.current = () => Promise.resolve(data);
+ render( );
+
+ await screen.findByText(/advanced nahw exercises/);
+ expect(screen.getByText("8")).toBeInTheDocument();
+ expect(
+ screen.getByText("advanced nahw exercises").closest("tr")
+ ).toBeTruthy();
+ });
+
+ it("renders an error state when the analytics service fails", async () => {
+ serviceState.current = () =>
+ Promise.reject(new Error("Service unavailable"));
+ render( );
+
+ expect(
+ await screen.findByText(/failed to load search analytics/i)
+ ).toBeInTheDocument();
+ expect(screen.getByText("Service unavailable")).toBeInTheDocument();
+ });
+
+ it("renders weekly trends chart sections", async () => {
+ serviceState.current = () => Promise.resolve(makeAnalytics());
+ render( );
+
+ await screen.findByText("Search Analytics");
+ expect(
+ screen.getByText("Weekly Query Volume")
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText("Click-Through Rate Trend")
+ ).toBeInTheDocument();
+ });
+});
diff --git a/__tests__/admin/admin-search-analytics.service.test.js b/__tests__/admin/admin-search-analytics.service.test.js
new file mode 100644
index 00000000..e271565e
--- /dev/null
+++ b/__tests__/admin/admin-search-analytics.service.test.js
@@ -0,0 +1,101 @@
+/**
+ * Admin search-analytics service — contract tests (#326).
+ * ------------------------------------------------------------------
+ * The service in `lib/actions/admin-search-analytics.js` is the seam the
+ * admin search-analytics page calls for the snapshot. These tests pin the
+ * resolved shape the UI depends on and, crucially, that metrics without
+ * backend instrumentation resolve with `tracked: false` so the page renders
+ * a "not tracked yet" placeholder instead of a silent zero.
+ */
+import { describe, it, expect } from "vitest";
+import { fetchSearchAnalytics } from "@/lib/actions/admin-search-analytics";
+
+describe("fetchSearchAnalytics", () => {
+ it("resolves a snapshot with the documented shape", async () => {
+ const data = await fetchSearchAnalytics();
+ expect(typeof data.generatedAt).toBe("string");
+ expect(typeof data.dateRange).toBe("object");
+ expect(typeof data.dateRange.from).toBe("string");
+ expect(typeof data.dateRange.to).toBe("string");
+ });
+
+ it("exposes all four total metrics with the correct shape", async () => {
+ const data = await fetchSearchAnalytics();
+ for (const key of [
+ "totalQueries",
+ "uniqueQueries",
+ "zeroResultQueries",
+ "avgClickThroughRate",
+ ]) {
+ expect(data.totals[key]).toBeDefined();
+ expect(typeof data.totals[key].label).toBe("string");
+ expect(typeof data.totals[key].tracked).toBe("boolean");
+ }
+ expect(typeof data.totals.totalQueries.value).toBe("number");
+ expect(typeof data.totals.uniqueQueries.value).toBe("number");
+ expect(typeof data.totals.zeroResultQueries.value).toBe("number");
+ // avgClickThroughRate may be null when not tracked
+ expect(
+ data.totals.avgClickThroughRate.value === null ||
+ typeof data.totals.avgClickThroughRate.value === "number"
+ ).toBe(true);
+ });
+
+ it("marks all metrics as not tracked when backend is stubbed", async () => {
+ const data = await fetchSearchAnalytics();
+ expect(data.totals.totalQueries.tracked).toBe(false);
+ expect(data.totals.uniqueQueries.tracked).toBe(false);
+ expect(data.totals.zeroResultQueries.tracked).toBe(false);
+ expect(data.totals.avgClickThroughRate.tracked).toBe(false);
+ });
+
+ it("provides an empty top queries list", async () => {
+ const data = await fetchSearchAnalytics();
+ expect(Array.isArray(data.topQueries)).toBe(true);
+ expect(data.topQueries.length).toBe(0);
+ });
+
+ it("provides an empty zero-result queries list", async () => {
+ const data = await fetchSearchAnalytics();
+ expect(Array.isArray(data.zeroResultQueries)).toBe(true);
+ expect(data.zeroResultQueries.length).toBe(0);
+ });
+
+ it("provides weekly trends with the correct shape", async () => {
+ const data = await fetchSearchAnalytics();
+ expect(Array.isArray(data.weeklyTrends)).toBe(true);
+ expect(data.weeklyTrends.length).toBeGreaterThan(0);
+ for (const week of data.weeklyTrends) {
+ expect(typeof week.week).toBe("string");
+ expect(typeof week.queries).toBe("number");
+ expect(typeof week.zeroResults).toBe("number");
+ expect(
+ week.clickThroughRate === null ||
+ typeof week.clickThroughRate === "number"
+ ).toBe(true);
+ }
+ });
+
+ it("accepts custom date range parameters", async () => {
+ const from = "2026-01-01T00:00:00.000Z";
+ const to = "2026-01-31T23:59:59.999Z";
+ const data = await fetchSearchAnalytics({ from, to });
+ expect(data.dateRange.from).toBe(from);
+ expect(data.dateRange.to).toBe(to);
+ });
+
+ it("falls back to default date range when parameters are omitted", async () => {
+ const data = await fetchSearchAnalytics();
+ // The from date should be approximately 30 days ago
+ const fromDate = new Date(data.dateRange.from);
+ const toDate = new Date(data.dateRange.to);
+ const diffDays = Math.round((toDate - fromDate) / (1000 * 60 * 60 * 24));
+ expect(diffDays).toBeGreaterThanOrEqual(29);
+ expect(diffDays).toBeLessThanOrEqual(31);
+ });
+
+ it("includes an avgClickThroughRate metric with unit %", async () => {
+ const data = await fetchSearchAnalytics();
+ expect(data.totals.avgClickThroughRate.unit).toBe("%");
+ });
+});
diff --git a/app/[locale]/admin/analytics/search/page.jsx b/app/[locale]/admin/analytics/search/page.jsx
new file mode 100644
index 00000000..d52533f9
--- /dev/null
+++ b/app/[locale]/admin/analytics/search/page.jsx
@@ -0,0 +1,584 @@
+"use client";
+
+import { useEffect, useState, useMemo } from "react";
+import { PageShell } from "@/components/ui/page-shell";
+import { PageHeader } from "@/components/ui/page-header";
+import {
+ Card,
+ CardHeader,
+ CardTitle,
+ CardDescription,
+ CardContent,
+} from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { Skeleton } from "@/components/ui/skeleton";
+import { EmptyState } from "@/components/ui/empty-state";
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import {
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+ ChartLegend,
+ ChartLegendContent,
+} from "@/components/ui/chart";
+import {
+ BarChart,
+ Bar,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Line,
+ LineChart,
+} from "recharts";
+import {
+ Search,
+ TrendingUp,
+ TrendingDown,
+ BarChart3,
+ Calendar,
+ MousePointerClick,
+ AlertTriangle,
+ ArrowUpRight,
+ ArrowDownRight,
+ Minus,
+ FileQuestion,
+ Eye,
+} from "lucide-react";
+import { cn } from "@/lib/utils";
+import { poppins_400, poppins_500, poppins_600 } from "@/lib/config/font.config";
+import { fetchSearchAnalytics } from "@/lib/actions/admin-search-analytics";
+
+const COLORS = {
+ queries: "#009900",
+ zeroResults: "#dc2626",
+ accent: "#265902",
+ ctr: "#00cc66",
+};
+
+const trendsChartConfig = {
+ queries: { label: "Queries", color: COLORS.queries },
+ zeroResults: { label: "Zero Results", color: COLORS.zeroResults },
+};
+
+const ctrChartConfig = {
+ clickThroughRate: { label: "Click-Through Rate", color: COLORS.ctr },
+};
+
+function TrendBadge({ value }) {
+ if (value === null || value === undefined) return — ;
+
+ if (value > 0) {
+ return (
+
+
+ {Math.abs(value).toFixed(1)}%
+
+ );
+ }
+ if (value < 0) {
+ return (
+
+
+ {Math.abs(value).toFixed(1)}%
+
+ );
+ }
+ return (
+
+
+ 0%
+
+ );
+}
+
+function StatCard({ icon: Icon, label, metric }) {
+ return (
+
+
+
+
+
+ {label}
+
+ {metric.tracked ? (
+
+ {metric.unit === "%"
+ ? metric.value !== null
+ ? `${metric.value.toFixed(1)}%`
+ : "—"
+ : metric.value.toLocaleString()}
+
+ ) : (
+
+ —
+
+ )}
+ {!metric.tracked && (
+
+ Not tracked yet
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+function TopQueriesTable({ queries, tracked }) {
+ if (!tracked || queries.length === 0) {
+ return (
+
+
+
+
+
+
+
+ Top Queries
+
+
+ {tracked ? "No data yet" : "Not tracked yet"}
+
+
+
+ {tracked
+ ? "No search queries have been recorded in this period."
+ : "Search query events are not being recorded yet, so no top queries are available."}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ #
+ Query
+ Searches
+ Click-Through Rate
+ Trend (WoW)
+
+
+
+ {queries.map((q, index) => (
+
+ {index + 1}
+
+ {q.query}
+
+
+ {q.count.toLocaleString()}
+
+
+ {q.clickThroughRate !== null ? `${q.clickThroughRate.toFixed(1)}%` : "—"}
+
+
+
+
+
+ ))}
+
+
+ );
+}
+
+function ZeroResultQueriesList({ queries, tracked }) {
+ if (!tracked || queries.length === 0) {
+ return (
+
+
+
+
+
+
+
+ Zero-Result Queries
+
+
+ {tracked ? "No data yet" : "Not tracked yet"}
+
+
+
+ {tracked
+ ? "No zero-result queries have been recorded in this period."
+ : "Zero-result search events are not being recorded yet. This list will surface content gaps once tracking is enabled."}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ #
+ Query
+ Searches
+ Last Searched
+
+
+
+ {queries.map((q, index) => (
+
+ {index + 1}
+
+ {q.query}
+
+
+ {q.count.toLocaleString()}
+
+
+ {new Date(q.lastSearched).toLocaleDateString()}
+
+
+ ))}
+
+
+ );
+}
+
+export default function SearchAnalyticsPage() {
+ const [data, setData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [timeRange, setTimeRange] = useState("30d");
+
+ useEffect(() => {
+ let active = true;
+ setLoading(true);
+ setError(null);
+
+ const now = new Date();
+ let from;
+
+ switch (timeRange) {
+ case "7d":
+ from = new Date(now);
+ from.setDate(from.getDate() - 7);
+ break;
+ case "14d":
+ from = new Date(now);
+ from.setDate(from.getDate() - 14);
+ break;
+ case "30d":
+ from = new Date(now);
+ from.setDate(from.getDate() - 30);
+ break;
+ case "90d":
+ from = new Date(now);
+ from.setDate(from.getDate() - 90);
+ break;
+ case "1y":
+ from = new Date(now);
+ from.setFullYear(from.getFullYear() - 1);
+ break;
+ default:
+ from = new Date(now);
+ from.setDate(from.getDate() - 30);
+ }
+
+ fetchSearchAnalytics({ from: from.toISOString(), to: now.toISOString() })
+ .then((analytics) => {
+ if (active) {
+ setData(analytics);
+ setLoading(false);
+ }
+ })
+ .catch((err) => {
+ if (active) {
+ setError(err?.message || "Failed to load search analytics");
+ setLoading(false);
+ }
+ });
+
+ return () => {
+ active = false;
+ };
+ }, [timeRange]);
+
+ if (loading) {
+ return (
+
+
+
+ {Array.from({ length: 4 }).map((_, index) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
+
+ );
+ }
+
+ const { totals, topQueries, zeroResultQueries, weeklyTrends } = data;
+
+ return (
+
+
+
+
+
+
+
+ Last 7 Days
+ Last 14 Days
+ Last 30 Days
+ Last 90 Days
+ Last Year
+
+
+ }
+ />
+
+ {/* Summary Stats */}
+
+
+
+
+
+
+
+ {/* Weekly Trends Charts */}
+
+ {/* Query Volume & Zero Results Trend */}
+
+
+
+
+ Weekly Query Volume
+
+
+ Total searches and zero-result queries over time
+
+
+
+ {weeklyTrends.some((w) => w.queries > 0 || w.zeroResults > 0) ? (
+
+
+
+
+
+ }
+ />
+
+
+ } />
+
+
+ ) : (
+
+
+
+
+
+
+ Not tracked yet
+
+
+ Search event tracking is not enabled yet. Once enabled, weekly query volume and zero-result trends will appear here.
+
+
+
+ )}
+
+
+
+ {/* Click-Through Rate Trend */}
+
+
+
+
+ Click-Through Rate Trend
+
+
+ Average click-through rate across search results per week
+
+
+
+ {weeklyTrends.some((w) => w.clickThroughRate !== null) ? (
+
+
+
+
+ `${val}%`}
+ />
+
+ value !== null ? `${Number(value).toFixed(1)}%` : "—"
+ }
+ />
+ }
+ />
+
+ } />
+
+
+ ) : (
+
+
+
+
+
+
+ Not tracked yet
+
+
+ Click-through rate data is not available yet. This chart will populate once search-result interactions are instrumented.
+
+
+
+ )}
+
+
+
+
+ {/* Top Queries Table */}
+
+
+
+
+ Top Queries
+
+
+ Most frequently searched terms with click-through rates and week-over-week trends
+
+
+
+
+
+
+
+ {/* Zero-Result Queries — Content Gap Signal */}
+
+
+
+
+ Zero-Result Queries
+
+
+ Searches that returned no results — these signal content gaps that can inform acquisition decisions
+
+
+
+
+
+
+
+ );
+}
diff --git a/lib/actions/admin-search-analytics.js b/lib/actions/admin-search-analytics.js
new file mode 100644
index 00000000..0d2a029f
--- /dev/null
+++ b/lib/actions/admin-search-analytics.js
@@ -0,0 +1,97 @@
+/**
+ * Admin search-analytics — top queries, zero-result queries, WoW trends.
+ * ---------------------------------------------------------------------------
+ * **STUBBED.** Resolves a representative search-analytics snapshot so the admin
+ * search-analytics page (#326) can be built and reviewed before the backend
+ * analytics endpoints ship. Every metric carries an explicit `tracked` flag:
+ * metrics the platform does not instrument yet resolve with `tracked: false`
+ * so the UI renders an explicit "not tracked yet" placeholder instead of a
+ * silent zero (see the issue's acceptance criteria).
+ *
+ * TODO(backend): GET /api/admin/analytics/search?from=&to=
+ * - Auth: requires a super-admin session token (server-side tier check).
+ * - 200 → the search-analytics shape documented below.
+ *
+ * Search analytics shape:
+ * {
+ * generatedAt: string,
+ * dateRange: { from: string, to: string },
+ * totals: {
+ * totalQueries: { label: string, value: number, tracked: boolean },
+ * uniqueQueries: { label: string, value: number, tracked: boolean },
+ * zeroResultQueries: { label: string, value: number, tracked: boolean },
+ * avgClickThroughRate: { label: string, value: number | null, tracked: boolean, unit: string },
+ * },
+ * topQueries: [
+ * { query: string, count: number, clickThroughRate: number, trend: number | null },
+ * ],
+ * zeroResultQueries: [
+ * { query: string, count: number, lastSearched: string },
+ * ],
+ * weeklyTrends: [
+ * { week: string, queries: number, zeroResults: number, clickThroughRate: number | null },
+ * ],
+ * }
+ */
+
+function withResolved(value) {
+ return Promise.resolve(value);
+}
+
+/**
+ * Fetch the platform-wide search analytics snapshot.
+ *
+ * TODO(backend):
+ * return axiosInstance
+ * .get("/api/admin/analytics/search", { params: { from, to } })
+ * .then((res) => res.data);
+ *
+ * @param {object} [options]
+ * @param {string} [options.from] - ISO date string for the start of the range
+ * @param {string} [options.to] - ISO date string for the end of the range
+ * @returns {Promise} the search-analytics snapshot documented above.
+ */
+export async function fetchSearchAnalytics({ from, to } = {}) {
+ const now = new Date();
+ const thirtyDaysAgo = new Date(now);
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
+
+ return withResolved({
+ generatedAt: now.toISOString(),
+ dateRange: {
+ from: from || thirtyDaysAgo.toISOString(),
+ to: to || now.toISOString(),
+ },
+ totals: {
+ totalQueries: {
+ label: "Total Queries",
+ value: 0,
+ tracked: false,
+ },
+ uniqueQueries: {
+ label: "Unique Queries",
+ value: 0,
+ tracked: false,
+ },
+ zeroResultQueries: {
+ label: "Zero-Result Queries",
+ value: 0,
+ tracked: false,
+ },
+ avgClickThroughRate: {
+ label: "Avg. Click-Through Rate",
+ value: null,
+ tracked: false,
+ unit: "%",
+ },
+ },
+ topQueries: [],
+ zeroResultQueries: [],
+ weeklyTrends: [
+ { week: "Week 1", queries: 0, zeroResults: 0, clickThroughRate: null },
+ { week: "Week 2", queries: 0, zeroResults: 0, clickThroughRate: null },
+ { week: "Week 3", queries: 0, zeroResults: 0, clickThroughRate: null },
+ { week: "Week 4", queries: 0, zeroResults: 0, clickThroughRate: null },
+ ],
+ });
+}