diff --git a/apps/apollo-vertex/templates/dashboard/ExpandedInsightContent.tsx b/apps/apollo-vertex/templates/dashboard/ExpandedInsightContent.tsx
new file mode 100644
index 000000000..e3321b5cb
--- /dev/null
+++ b/apps/apollo-vertex/templates/dashboard/ExpandedInsightContent.tsx
@@ -0,0 +1,338 @@
+"use client";
+
+import { useState } from "react";
+import { Badge } from "@/components/ui/badge";
+
+// --- Sample data ---
+
+const trendData = {
+ weeks: ["W1", "W2", "W3", "W4", "W5", "W6", "W7", "W8"],
+ series: [
+ {
+ label: "Wrong size/fit",
+ color: "bg-chart-1",
+ stroke: "stroke-chart-1",
+ values: [31, 33, 34, 35, 36, 37, 39, 41],
+ },
+ {
+ label: "Damaged in transit",
+ color: "bg-chart-2",
+ stroke: "stroke-chart-2",
+ values: [25, 24, 26, 23, 22, 24, 23, 21],
+ },
+ {
+ label: "Not as described",
+ color: "bg-chart-3",
+ stroke: "stroke-chart-3",
+ values: [20, 19, 18, 19, 18, 17, 18, 17],
+ },
+ ],
+ takeaway:
+ "Fit-related returns have grown steadily over 8 weeks (+32%), while damage and description issues remain flat.",
+};
+
+const categoryBreakdown = [
+ { category: "Women's Apparel", pct: 48, highlight: true },
+ { category: "Footwear", pct: 27 },
+ { category: "Men's Apparel", pct: 16 },
+ { category: "Accessories", pct: 9 },
+];
+const categoryInsight =
+ "Women's apparel and footwear account for 75% of all fit-related returns. Sizing inconsistency across brands is the primary driver.";
+
+const topProducts = [
+ {
+ name: "Slim Fit Chinos — Navy",
+ returnRate: 18.4,
+ issue: "Wrong size",
+ impact: "$12,400",
+ },
+ {
+ name: "Running Shoe Pro V2",
+ returnRate: 15.2,
+ issue: "Wrong fit",
+ impact: "$9,800",
+ },
+ {
+ name: "Wrap Dress — Floral",
+ returnRate: 14.7,
+ issue: "Wrong size",
+ impact: "$8,200",
+ },
+ {
+ name: "Oversized Hoodie — Black",
+ returnRate: 12.1,
+ issue: "Too large",
+ impact: "$6,900",
+ },
+ {
+ name: "Ankle Boot — Tan",
+ returnRate: 11.8,
+ issue: "Wrong fit",
+ impact: "$5,400",
+ },
+];
+
+const recommendations = [
+ {
+ action: "Deploy dynamic size recommendation for top 3 SKUs",
+ impact: "Est. 22% reduction in fit returns",
+ priority: "High",
+ },
+ {
+ action: "Add fit-specific review prompts to product pages",
+ impact: "Improve size confidence pre-purchase",
+ priority: "Medium",
+ },
+ {
+ action: "Flag brands with >15% size variance for supplier review",
+ impact: "Address root cause across catalog",
+ priority: "Medium",
+ },
+];
+
+const suggestedPrompts = [
+ "Why are fit-related returns increasing?",
+ "Which products are driving return volume?",
+ "What orders are at risk of return?",
+];
+
+// --- Components ---
+
+function TrendChart({ data }: { data: typeof trendData }) {
+ const allValues = data.series.flatMap((s) => s.values);
+ const max = Math.max(...allValues);
+ const h = 60;
+ const w = 180;
+ const step = w / (data.weeks.length - 1);
+
+ return (
+
+
+
+ Trend over time
+
+
+ 8-week view of the top 3 return reasons
+
+
+
+
+ {data.series.map((s) => (
+
+ ))}
+
+
+
+ );
+}
+
+function CategoryBreakdown() {
+ return (
+
+
+
+ Category breakdown
+
+
+ Where "Wrong size/fit" returns are concentrated
+
+
+
+ {categoryBreakdown.map((cat) => (
+
+
+ {cat.category}
+ {cat.pct}%
+
+
+
+ ))}
+
+
+
+ );
+}
+
+function TopProducts() {
+ return (
+
+
+
+ Top products driving issues
+
+
+ Ranked by return rate with revenue impact
+
+
+
+
+ Product
+ Return %
+ Issue
+ Impact
+
+ {topProducts.map((p) => (
+
+ {p.name}
+
+ {p.returnRate}%
+
+
+ {p.issue}
+
+
+ {p.impact}
+
+
+ ))}
+
+
+ );
+}
+
+function Recommendations() {
+ return (
+
+
+
+ Recommended actions
+
+
+ AI-assisted next steps based on current data
+
+
+
+ {recommendations.map((rec, i) => (
+
+
+
+ {i + 1}
+
+
+ {rec.priority}
+
+
+
{rec.action}
+
+ {rec.impact}
+
+
+ ))}
+
+
+ );
+}
+
+// --- Exports ---
+
+export type DrilldownTab =
+ | "overview"
+ | "trend"
+ | "categories"
+ | "products"
+ | "actions";
+
+export const drilldownTabs: { key: DrilldownTab; label: string }[] = [
+ { key: "overview", label: "Overview" },
+ { key: "categories", label: "Categories" },
+ { key: "products", label: "Products" },
+ { key: "actions", label: "Actions" },
+];
+
+export function DrilldownTabContent({ tab }: { tab: DrilldownTab }) {
+ if (tab === "trend") return ;
+ if (tab === "categories") return ;
+ if (tab === "products") return ;
+ if (tab === "actions") return ;
+ return null; // "overview" is handled by the original card content
+}
+
+export function AutopilotPrompts({
+ onPromptSelect,
+}: {
+ onPromptSelect?: (prompt: string) => void;
+}) {
+ const [pressedPrompt, setPressedPrompt] = useState(null);
+
+ return (
+
+
+

+

+
Ask Autopilot
+
+
+ {suggestedPrompts.map((prompt) => (
+
+ ))}
+
+
+ );
+}
diff --git a/apps/apollo-vertex/templates/dashboard/InsightGrid.tsx b/apps/apollo-vertex/templates/dashboard/InsightGrid.tsx
new file mode 100644
index 000000000..e57ca42a2
--- /dev/null
+++ b/apps/apollo-vertex/templates/dashboard/InsightGrid.tsx
@@ -0,0 +1,508 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { ArrowUpRight, Maximize2, Minimize2 } from "lucide-react";
+import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ cardBgStyle,
+ getInsightCardClasses,
+ type CardConfig,
+ type InsightCardConfig,
+ type LayoutConfig,
+} from "./glow-config";
+import { InsightCardBody } from "./insight-card-renderers";
+import { useDashboardData } from "./DashboardDataProvider";
+import {
+ type DrilldownTab,
+ drilldownTabs,
+ DrilldownTabContent,
+ AutopilotPrompts,
+} from "./ExpandedInsightContent";
+
+const sizeToFr: Record = { sm: "1fr", md: "2fr", lg: "1fr" };
+
+// --- Shared card inner content ---
+
+interface InsightCardInnerProps {
+ cfg: InsightCardConfig;
+ cardIndex: number;
+ shared: string;
+ cards: CardConfig;
+ isExpanding: boolean;
+ isThis: boolean;
+ phase: ExpandPhase;
+ viewMode: "desktop" | "compact" | "stacked";
+ drilldownTab: DrilldownTab;
+ onDrilldownTabChange: (tab: DrilldownTab) => void;
+ onExpandClick: () => void;
+ onAutopilotOpen?: () => void;
+ isAutopilotActive?: boolean;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+function InsightCardInner({
+ cfg,
+ cardIndex,
+ shared,
+ cards,
+ isExpanding,
+ isThis,
+ phase,
+ viewMode,
+ onExpandClick,
+ onAutopilotOpen,
+ drilldownTab,
+ onDrilldownTabChange,
+ isAutopilotActive = false,
+ className = "",
+ style,
+}: InsightCardInnerProps) {
+ const { data } = useDashboardData();
+ const cardTitle = data.insightCards[cardIndex]?.title ?? cfg.content.title;
+ const hasDrilldown = cfg.content.chartType === "horizontal-bars";
+ const isExpandedWithDrilldown =
+ isThis && isExpanding && hasDrilldown && phase === "full";
+ const classes = getInsightCardClasses(cfg.content, viewMode);
+ const isInteractive = cfg.interaction !== "static";
+
+ return (
+
+
+
+ {cardTitle}
+
+ {isInteractive && cfg.interaction === "expand" && (
+
+
+ {onAutopilotOpen && (
+
+ )}
+
+
+
+ )}
+ {isInteractive && cfg.interaction === "navigate" && !isThis && (
+
+
+
+ )}
+ {/* Drilldown tabs — below title when expanded */}
+ {isThis &&
+ isExpanding &&
+ hasDrilldown &&
+ (phase === "height" || phase === "full") &&
+ (() => {
+ const visibleTabs = drilldownTabs.slice(0, 4);
+ const overflowTabs = drilldownTabs.slice(4);
+ const isOverflowActive = overflowTabs.some(
+ (t) => t.key === drilldownTab,
+ );
+ return (
+
+ {visibleTabs.map((tab) => (
+
+ ))}
+ {overflowTabs.length > 0 && (
+
+
+
+
+ )}
+
+ );
+ })()}
+
+ {isExpandedWithDrilldown ? (
+ /* Expanded with drilldown — unified layout for all tabs */
+
+
+
+ {drilldownTab === "overview" ? (
+
+ ) : (
+
+ )}
+
+
+
+
onAutopilotOpen?.()} />
+
+
+ ) : (
+ /* Default card content — not expanded or no drilldown */
+
+
+
+ )}
+ {/* Non-drilldown expanded content (other card types) */}
+ {isThis &&
+ isExpanding &&
+ !hasDrilldown &&
+ (phase === "height" || phase === "full") && (
+
+ {phase === "full" ? (
+
+
+ Additional content
+
+
+ ) : (
+
+ )}
+
+ )}
+
+ );
+}
+
+// --- Main grid ---
+
+type ExpandPhase = "idle" | "width" | "height" | "full";
+
+export function InsightGrid({
+ layout,
+ shared,
+ cards,
+ viewMode = "desktop",
+ onAutopilotOpen,
+ autopilotActiveIdx,
+}: {
+ layout: LayoutConfig;
+ shared: string;
+ cards: CardConfig;
+ viewMode?: "desktop" | "compact" | "stacked";
+ onAutopilotOpen?: (sourceTitle: string, idx: number) => void;
+ autopilotActiveIdx?: number | null;
+}) {
+ const { data } = useDashboardData();
+ const [expandedIdx, setExpandedIdx] = useState(null);
+ const [phase, setPhase] = useState("idle");
+ const [drilldownTab, setDrilldownTab] = useState("overview");
+
+ useEffect(() => {
+ if (expandedIdx === null) {
+ setPhase("idle");
+ setDrilldownTab("overview");
+ return;
+ }
+ requestAnimationFrame(() => setPhase("width"));
+ const t1 = setTimeout(() => setPhase("height"), 300);
+ const t2 = setTimeout(() => setPhase("full"), 600);
+ return () => {
+ clearTimeout(t1);
+ clearTimeout(t2);
+ };
+ }, [expandedIdx]);
+
+ const visibleCards = layout.insightCards
+ .map((cfg, i) => {
+ const dataCard = data.insightCards[i];
+ const merged = dataCard
+ ? {
+ ...cfg,
+ size: dataCard.size ?? cfg.size,
+ interaction: dataCard.interaction ?? cfg.interaction,
+ content: {
+ ...cfg.content,
+ title: dataCard.title ?? cfg.content.title,
+ },
+ }
+ : cfg;
+ return { cfg: merged, idx: i };
+ })
+ .filter(({ cfg }) => cfg.visible);
+ const rows: (typeof visibleCards)[] = [];
+ for (let i = 0; i < visibleCards.length; i += 2) {
+ rows.push(visibleCards.slice(i, i + 2));
+ }
+
+ const isExpanding = expandedIdx !== null;
+ const expandedRow = isExpanding
+ ? rows.findIndex((row) => row.some(({ idx }) => idx === expandedIdx))
+ : -1;
+
+ const handleClick = (cfg: InsightCardConfig, idx: number) => {
+ if (cfg.interaction === "expand") {
+ setExpandedIdx(expandedIdx === idx ? null : idx);
+ }
+ };
+
+ // Build grid-template-rows
+ let rowTemplates: string[];
+ if (viewMode === "compact") {
+ rowTemplates = visibleCards.map(({ idx }) => {
+ if (!isExpanding) return "1fr";
+ if (idx === expandedIdx) return "1fr";
+ if (phase !== "idle") return "0fr";
+ return "1fr";
+ });
+ } else {
+ rowTemplates = rows.map((_, rowIndex) => {
+ const isOtherRow = isExpanding && rowIndex !== expandedRow;
+ if (isOtherRow && (phase === "height" || phase === "full")) return "0fr";
+ return "1fr";
+ });
+ }
+
+ const sharedProps = {
+ shared,
+ cards,
+ isExpanding,
+ phase,
+ viewMode,
+ drilldownTab,
+ onDrilldownTabChange: setDrilldownTab,
+ };
+
+ return (
+
+ {viewMode === "compact"
+ ? visibleCards.map(({ cfg, idx }) => {
+ const isThis = idx === expandedIdx;
+ const isOther = isExpanding && !isThis;
+ return (
+
+ handleClick(cfg, idx)}
+ onAutopilotOpen={
+ onAutopilotOpen
+ ? () =>
+ onAutopilotOpen(
+ data.insightCards[idx]?.title ?? cfg.content.title,
+ idx,
+ )
+ : undefined
+ }
+ isAutopilotActive={autopilotActiveIdx === idx}
+ className="h-full"
+ />
+
+ );
+ })
+ : rows.map((row, rowIndex) => {
+ const isRowWithExpanded = rowIndex === expandedRow;
+ const isOtherRow = isExpanding && !isRowWithExpanded;
+ const cols = row
+ .map(({ cfg, idx }) => {
+ if (!isExpanding)
+ return cfg.size === "lg" ? "1fr" : sizeToFr[cfg.size];
+ if (idx === expandedIdx)
+ return phase === "idle"
+ ? cfg.size === "lg"
+ ? "1fr"
+ : sizeToFr[cfg.size]
+ : "1fr";
+ if (isRowWithExpanded)
+ return phase === "idle"
+ ? cfg.size === "lg"
+ ? "1fr"
+ : sizeToFr[cfg.size]
+ : "0fr";
+ return cfg.size === "lg" ? "1fr" : sizeToFr[cfg.size];
+ })
+ .join(" ");
+ return (
+
idx).join("-")}
+ className="grid transition-all duration-300 ease-in-out overflow-hidden min-h-0"
+ style={
+ {
+ gridTemplateColumns: cols,
+ gap: isRowWithExpanded && phase !== "idle" ? 0 : layout.gap,
+ opacity:
+ isOtherRow && (phase === "height" || phase === "full")
+ ? 0
+ : 1,
+ } as React.CSSProperties
+ }
+ >
+ {row.map(({ cfg, idx }) => {
+ const isThis = idx === expandedIdx;
+ const isSibling = isExpanding && !isThis && isRowWithExpanded;
+ return (
+ handleClick(cfg, idx)}
+ onAutopilotOpen={
+ onAutopilotOpen
+ ? () =>
+ onAutopilotOpen(
+ data.insightCards[idx]?.title ??
+ cfg.content.title,
+ idx,
+ )
+ : undefined
+ }
+ isAutopilotActive={autopilotActiveIdx === idx}
+ style={{
+ opacity: isSibling && phase !== "idle" ? 0 : 1,
+ transform:
+ isSibling && phase !== "idle"
+ ? "scale(0.95)"
+ : "scale(1)",
+ }}
+ />
+ );
+ })}
+
+ );
+ })}
+
+ );
+}
diff --git a/apps/apollo-vertex/templates/dashboard/PromptBar.tsx b/apps/apollo-vertex/templates/dashboard/PromptBar.tsx
new file mode 100644
index 000000000..9bd863f88
--- /dev/null
+++ b/apps/apollo-vertex/templates/dashboard/PromptBar.tsx
@@ -0,0 +1,193 @@
+"use client";
+
+import { useState } from "react";
+import { MessagesSquare, Minimize2 } from "lucide-react";
+import { Badge } from "@/components/ui/badge";
+import type { CardConfig, CardGradient } from "./glow-config";
+import { useDashboardData } from "./DashboardDataProvider";
+
+function cardBgStyle(
+ bg: string,
+ opacity: number,
+ gradient: CardGradient,
+): React.CSSProperties {
+ if (gradient.enabled) {
+ const alpha = gradient.opacity / 100;
+ return {
+ "--card-bg-override": `linear-gradient(${gradient.angle}deg, color-mix(in srgb, ${gradient.start} ${alpha * 100}%, transparent), color-mix(in srgb, ${gradient.end} ${alpha * 100}%, transparent))`,
+ borderColor: "transparent",
+ } as React.CSSProperties;
+ }
+ const value =
+ bg === "white"
+ ? `rgba(255,255,255,${opacity / 100})`
+ : `color-mix(in srgb, var(--${bg}) ${opacity}%, transparent)`;
+ return { "--card-bg-override": value } as React.CSSProperties;
+}
+
+export function PromptBar({
+ shared,
+ cards,
+ isExpanded = false,
+ onSubmit,
+ onExpand,
+ onCollapse,
+}: {
+ shared: string;
+ cards: CardConfig;
+ isExpanded?: boolean;
+ onSubmit?: (query: string) => void;
+ onExpand?: () => void;
+ onCollapse?: () => void;
+}) {
+ const { data } = useDashboardData();
+ const [value, setValue] = useState("");
+ const hasInput = value.trim().length > 0;
+
+ const handleSubmit = () => {
+ if (hasInput && onSubmit) {
+ onSubmit(value);
+ }
+ };
+
+ const handleChipClick = (suggestion: string) => {
+ setValue(suggestion);
+ onSubmit?.(suggestion);
+ };
+
+ return (
+
+ {/* Expanded response area */}
+ {isExpanded && (
+
+
+
+

+

+
+ Autopilot
+
+
+ {onCollapse && (
+
+ )}
+
+
+
+ Responses will appear here
+
+
+
+
+ )}
+ {/* Suggestion badges — hidden when expanded */}
+ {!isExpanded && (
+
+
+
+
+ handleChipClick(
+ data.promptSuggestions[0] ?? "Show me top risk factors",
+ )
+ }
+ >
+ {data.promptSuggestions[0] ?? "Show me top risk factors"}
+
+
+ handleChipClick(
+ data.promptSuggestions[1] ??
+ "Compare Q1 vs Q2 performance",
+ )
+ }
+ >
+ {data.promptSuggestions[1] ?? "Compare Q1 vs Q2 performance"}
+
+
+
+
+ )}
+ {/* Input bar */}
+
+
setValue(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") handleSubmit();
+ }}
+ placeholder={data.promptPlaceholder}
+ className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
+ />
+
+
+
+
+
+
+ );
+}
diff --git a/apps/apollo-vertex/templates/dashboard/dev-controls-primitives.tsx b/apps/apollo-vertex/templates/dashboard/dev-controls-primitives.tsx
new file mode 100644
index 000000000..a779dd432
--- /dev/null
+++ b/apps/apollo-vertex/templates/dashboard/dev-controls-primitives.tsx
@@ -0,0 +1,113 @@
+"use client";
+
+export function Slider({
+ label,
+ value,
+ min,
+ max,
+ step,
+ onChange,
+ displayValue,
+}: {
+ label: string;
+ value: number;
+ min: number;
+ max: number;
+ step: number;
+ onChange: (v: number) => void;
+ displayValue?: string;
+}) {
+ return (
+
+
+ {label}
+ {displayValue ?? value}
+
+
onChange(Number(e.target.value))}
+ className="w-full h-1 accent-primary"
+ />
+
+ );
+}
+
+export function SelectControl({
+ label,
+ value,
+ options,
+ onChange,
+}: {
+ label: string;
+ value: string;
+ options: { label: string; value: string }[];
+ onChange: (v: string) => void;
+}) {
+ return (
+
+
{label}
+
+
+ );
+}
+
+export function Toggle({
+ label,
+ checked,
+ onChange,
+}: {
+ label: string;
+ checked: boolean;
+ onChange: (v: boolean) => void;
+}) {
+ return (
+
+ );
+}
+
+export function TextInput({
+ label,
+ value,
+ onChange,
+ placeholder,
+}: {
+ label: string;
+ value: string;
+ onChange: (v: string) => void;
+ placeholder?: string;
+}) {
+ return (
+
+
{label}
+
onChange(e.target.value)}
+ className="w-full h-7 rounded border bg-background px-1 text-xs"
+ placeholder={placeholder}
+ />
+
+ );
+}
diff --git a/apps/apollo-vertex/templates/dashboard/dev-controls-tabs.tsx b/apps/apollo-vertex/templates/dashboard/dev-controls-tabs.tsx
new file mode 100644
index 000000000..0ffc3c51d
--- /dev/null
+++ b/apps/apollo-vertex/templates/dashboard/dev-controls-tabs.tsx
@@ -0,0 +1,396 @@
+"use client";
+
+import {
+ SelectControl,
+ Slider,
+ TextInput,
+ Toggle,
+} from "./dev-controls-primitives";
+import {
+ bgColorOptions,
+ cardTypeOptions,
+ chartTypeOptions,
+ containerBgOptions,
+ insightOptions,
+ interactionOptions,
+ isCardInteraction,
+ isCardSize,
+ isChartType,
+ isInsightCardType,
+ primaryOptions,
+ sizeOptions,
+ type CardConfig,
+ type CardGradient,
+ type GlowConfig,
+ type InsightCardConfig,
+ type LayoutConfig,
+} from "./glow-config";
+
+function GradientSection({
+ gradient,
+ onChange,
+}: {
+ gradient: CardGradient;
+ onChange: (g: CardGradient) => void;
+}) {
+ const update = (partial: Partial) =>
+ onChange({ ...gradient, ...partial });
+
+ return (
+
+ update({ enabled: v })}
+ />
+ {gradient.enabled && (
+ <>
+ update({ start: v })}
+ />
+ update({ end: v })}
+ />
+ update({ angle: v })}
+ displayValue={`${gradient.angle}°`}
+ />
+ update({ opacity: v })}
+ displayValue={`${gradient.opacity}%`}
+ />
+ >
+ )}
+
+ );
+}
+
+export function GlowTab({
+ config,
+ onChange,
+}: {
+ config: GlowConfig;
+ onChange: (c: GlowConfig) => void;
+}) {
+ const update = (partial: Partial) =>
+ onChange({ ...config, ...partial });
+
+ return (
+
+ update({ start: v })}
+ />
+ update({ end: v })}
+ />
+ update({ containerOpacity: v })}
+ displayValue={`${config.containerOpacity}%`}
+ />
+ update({ fillOpacity: v })}
+ />
+ update({ startStopOpacity: v })}
+ />
+ update({ endStopOpacity: v })}
+ />
+ update({ endOffset: v })}
+ />
+
+ );
+}
+
+export function CardsTab({
+ config,
+ onChange,
+}: {
+ config: CardConfig;
+ onChange: (c: CardConfig) => void;
+}) {
+ const update = (partial: Partial) =>
+ onChange({ ...config, ...partial });
+
+ return (
+
+
Overview Card
+
update({ overviewBg: v })}
+ />
+ update({ overviewOpacity: v })}
+ displayValue={`${config.overviewOpacity}%`}
+ />
+ update({ overviewGradient: g })}
+ />
+
+
+ Insight Cards
+
+ update({ insightBg: v })}
+ />
+ update({ insightOpacity: v })}
+ displayValue={`${config.insightOpacity}%`}
+ />
+ update({ insightGradient: g })}
+ />
+
+ Prompt Bar
+ update({ promptBg: v })}
+ />
+ update({ promptOpacity: v })}
+ displayValue={`${config.promptOpacity}%`}
+ />
+ update({ promptGradient: g })}
+ />
+
+ Shared
+ update({ borderVisible: v })}
+ />
+ update({ backdropBlur: v })}
+ />
+
+ );
+}
+
+export function LayoutTab({
+ config,
+ onChange,
+}: {
+ config: LayoutConfig;
+ onChange: (c: LayoutConfig) => void;
+}) {
+ const update = (partial: Partial) =>
+ onChange({ ...config, ...partial });
+
+ const updateInsightCard = (
+ index: number,
+ partial: Partial,
+ ) => {
+ const cards = [...config.insightCards] as [
+ InsightCardConfig,
+ InsightCardConfig,
+ InsightCardConfig,
+ InsightCardConfig,
+ ];
+ cards[index] = { ...cards[index], ...partial };
+ update({ insightCards: cards });
+ };
+
+ return (
+
+
update({ containerBg: v })}
+ />
+ update({ gap: v })}
+ displayValue={`${config.gap}px`}
+ />
+ update({ padding: v })}
+ displayValue={`${config.padding}px`}
+ />
+ Left Column
+ update({ overviewRatio: v })}
+ />
+ update({ promptRatio: v })}
+ />
+
+ Insight Cards
+
+ {["Top Left", "Top Right", "Bottom Left", "Bottom Right"].map(
+ (label, i) => (
+
+
+ {label}
+ updateInsightCard(i, { visible: v })}
+ />
+
+ {config.insightCards[i].visible && (
+ <>
+
{
+ if (isCardSize(v)) updateInsightCard(i, { size: v });
+ }}
+ />
+ {
+ if (isInsightCardType(v))
+ updateInsightCard(i, {
+ content: { ...config.insightCards[i].content, type: v },
+ });
+ }}
+ />
+ {config.insightCards[i].content.type === "chart" && (
+ {
+ if (isChartType(v))
+ updateInsightCard(i, {
+ content: {
+ ...config.insightCards[i].content,
+ chartType: v,
+ },
+ });
+ }}
+ />
+ )}
+
+ updateInsightCard(i, {
+ content: { ...config.insightCards[i].content, title: v },
+ })
+ }
+ />
+ {
+ if (isCardInteraction(v))
+ updateInsightCard(i, { interaction: v });
+ }}
+ />
+ {config.insightCards[i].interaction === "navigate" && (
+ updateInsightCard(i, { navigateTo: v })}
+ placeholder="/preview/dashboard/..."
+ />
+ )}
+ >
+ )}
+
+ ),
+ )}
+
+ );
+}