@inkindcards/semantic-layer 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -811,6 +811,7 @@ function InspectableRegistry({ children }) {
811
811
  const [entries, setEntries] = react.useState(/* @__PURE__ */ new Map());
812
812
  const [inspectActive, setInspectActive] = react.useState(false);
813
813
  const [selectedId, setSelectedId] = react.useState(null);
814
+ const [modalTab, setModalTab] = react.useState(null);
814
815
  const register = react.useCallback((entry) => {
815
816
  setEntries((prev) => {
816
817
  const next = new Map(prev);
@@ -833,7 +834,9 @@ function InspectableRegistry({ children }) {
833
834
  inspectActive,
834
835
  setInspectActive,
835
836
  selectedId,
836
- setSelectedId
837
+ setSelectedId,
838
+ modalTab,
839
+ setModalTab
837
840
  };
838
841
  return /* @__PURE__ */ jsxRuntime.jsx(InspectableRegistryContext.Provider, { value, children });
839
842
  }
@@ -1078,6 +1081,229 @@ function generateMigrationPrompt(config) {
1078
1081
  );
1079
1082
  return parts.join("\n");
1080
1083
  }
1084
+
1085
+ // src/components/report-prompt-generator.ts
1086
+ var PALETTE = [
1087
+ "#3b82f6",
1088
+ "#10b981",
1089
+ "#f59e0b",
1090
+ "#ef4444",
1091
+ "#8b5cf6",
1092
+ "#ec4899",
1093
+ "#06b6d4",
1094
+ "#84cc16",
1095
+ "#f97316",
1096
+ "#6366f1"
1097
+ ];
1098
+ function generateReportPrompt(config) {
1099
+ const { dashboardTitle, layout, widgets, sharedFilters } = config;
1100
+ if (widgets.length === 0) {
1101
+ return `Create a new dashboard page called "${dashboardTitle}". Use @inkindcards/semantic-layer to connect to live data. Start by adding a DataCatalog so the user can browse available metrics and dimensions.`;
1102
+ }
1103
+ const parts = [];
1104
+ parts.push(`Create a dashboard page called "${dashboardTitle}" using the @inkindcards/semantic-layer SDK to query live data from the dbt Semantic Layer.`);
1105
+ parts.push("");
1106
+ parts.push("## Setup");
1107
+ parts.push("");
1108
+ parts.push("Install dependencies if not already present:");
1109
+ parts.push(" npm install @inkindcards/semantic-layer recharts");
1110
+ parts.push("");
1111
+ parts.push("Imports needed:");
1112
+ parts.push("```tsx");
1113
+ parts.push(`import { useSemanticQuery } from "@inkindcards/semantic-layer/react";`);
1114
+ const chartTypes = new Set(widgets.map((w) => w.type));
1115
+ const rechartsImports = ["ResponsiveContainer", "Tooltip", "Legend"];
1116
+ if (chartTypes.has("line")) rechartsImports.push("LineChart", "Line", "XAxis", "YAxis", "CartesianGrid");
1117
+ if (chartTypes.has("bar")) rechartsImports.push("BarChart", "Bar", "XAxis", "YAxis", "CartesianGrid");
1118
+ if (chartTypes.has("area")) rechartsImports.push("AreaChart", "Area", "XAxis", "YAxis", "CartesianGrid");
1119
+ if (chartTypes.has("pie")) rechartsImports.push("PieChart", "Pie", "Cell");
1120
+ const unique = [...new Set(rechartsImports)];
1121
+ if (unique.length > 3) {
1122
+ parts.push(`import { ${unique.join(", ")} } from "recharts";`);
1123
+ }
1124
+ parts.push("```");
1125
+ parts.push("");
1126
+ if (sharedFilters.length > 0) {
1127
+ parts.push("## Shared Filters");
1128
+ parts.push("");
1129
+ parts.push("Create state variables for cross-widget filtering. Render a filter bar at the top of the dashboard with a select/dropdown for each filter. All useSemanticQuery calls below should include the `filters` object so every widget responds to filter changes.");
1130
+ parts.push("");
1131
+ parts.push("```tsx");
1132
+ for (const f of sharedFilters) {
1133
+ parts.push(`const [filter_${sanitize(f.dimension.name)}, setFilter_${sanitize(f.dimension.name)}] = useState<string[]>([]);`);
1134
+ }
1135
+ parts.push("");
1136
+ parts.push("const filters: Record<string, string[]> = {");
1137
+ for (const f of sharedFilters) {
1138
+ parts.push(` "${f.dimension.name}": filter_${sanitize(f.dimension.name)},`);
1139
+ }
1140
+ parts.push("};");
1141
+ parts.push("```");
1142
+ parts.push("");
1143
+ parts.push("For each filter dropdown, query the distinct values of the dimension using useSemanticQuery with that dimension as a groupBy and any single metric, then populate the dropdown options from the returned rows.");
1144
+ parts.push("");
1145
+ }
1146
+ parts.push("## Layout");
1147
+ parts.push("");
1148
+ parts.push(layoutInstructions(layout, widgets));
1149
+ parts.push("");
1150
+ parts.push("## Color Palette");
1151
+ parts.push("");
1152
+ parts.push(`Use this consistent color palette for all charts: ${PALETTE.slice(0, 6).map((c) => `\`${c}\``).join(", ")}. Assign colors to metrics in order so the same metric has the same color everywhere.`);
1153
+ parts.push("");
1154
+ parts.push("## Widgets");
1155
+ parts.push("");
1156
+ for (let i = 0; i < widgets.length; i++) {
1157
+ const w = widgets[i];
1158
+ parts.push(`### ${i + 1}. ${w.title} (${widgetLabel(w.type)})`);
1159
+ parts.push("");
1160
+ parts.push(widgetPrompt(w, sharedFilters.length > 0));
1161
+ parts.push("");
1162
+ }
1163
+ parts.push("## Styling");
1164
+ parts.push("");
1165
+ parts.push("- Use a clean, modern design with rounded corners (border-radius: 12px) and subtle shadows on each card.");
1166
+ parts.push("- Background: white cards on a light gray page (#f9fafb).");
1167
+ parts.push("- Use system-ui / -apple-system font stack, 14px base size.");
1168
+ parts.push("- Each widget should be wrapped in a card with a title header, padding 20px, and a subtle 1px border (#e5e7eb).");
1169
+ parts.push("- Show a shimmer/skeleton loading state while isLoading is true.");
1170
+ parts.push("- Show a styled error banner if error is not null.");
1171
+ parts.push("- Format numbers with Intl.NumberFormat: use commas for large numbers, currency format for revenue/dollar metrics, percentage format for rate metrics.");
1172
+ parts.push("- Charts should fill their container width using ResponsiveContainer with height 300px.");
1173
+ parts.push("- On mobile (< 768px), stack everything in a single column.");
1174
+ parts.push("");
1175
+ return parts.join("\n");
1176
+ }
1177
+ function widgetPrompt(w, hasFilters, index) {
1178
+ const lines = [];
1179
+ const metricsArr = w.metrics.map((m) => `'${m.name}'`).join(", ");
1180
+ const groupByArr = w.groupBy.map((d) => `'${d.name}'`).join(", ");
1181
+ lines.push("Query:");
1182
+ lines.push("```tsx");
1183
+ lines.push(`const { data, isLoading, error } = useSemanticQuery({`);
1184
+ if (w.metrics.length > 0) lines.push(` metrics: [${metricsArr}],`);
1185
+ if (w.groupBy.length > 0) lines.push(` groupBy: [${groupByArr}],`);
1186
+ if (w.grain) lines.push(` grain: '${w.grain}',`);
1187
+ if (hasFilters) lines.push(` filters,`);
1188
+ lines.push(`});`);
1189
+ lines.push("```");
1190
+ lines.push("");
1191
+ switch (w.type) {
1192
+ case "line":
1193
+ case "area": {
1194
+ const ChartTag = w.type === "line" ? "LineChart" : "AreaChart";
1195
+ const SeriesTag = w.type === "line" ? "Line" : "Area";
1196
+ const xField = w.groupBy[0];
1197
+ lines.push(`Render a ${ChartTag} inside a ResponsiveContainer (height 300).`);
1198
+ if (xField) lines.push(`X-axis: "${xField.name}" (label: "${xField.displayName}").`);
1199
+ lines.push(`Add a ${SeriesTag} for each metric:`);
1200
+ for (let i = 0; i < w.metrics.length; i++) {
1201
+ const m = w.metrics[i];
1202
+ lines.push(` - dataKey="${m.name}" name="${m.displayName}" stroke="${PALETTE[i % PALETTE.length]}"${w.type === "area" ? ` fill="${PALETTE[i % PALETTE.length]}" fillOpacity={0.15}` : ""}`);
1203
+ }
1204
+ lines.push("Include CartesianGrid (strokeDasharray='3 3'), Tooltip, and Legend.");
1205
+ break;
1206
+ }
1207
+ case "bar": {
1208
+ const xField = w.groupBy[0];
1209
+ lines.push("Render a BarChart inside a ResponsiveContainer (height 300).");
1210
+ if (xField) lines.push(`X-axis: "${xField.name}" (label: "${xField.displayName}").`);
1211
+ lines.push("Add a Bar for each metric:");
1212
+ for (let i = 0; i < w.metrics.length; i++) {
1213
+ const m = w.metrics[i];
1214
+ lines.push(` - dataKey="${m.name}" name="${m.displayName}" fill="${PALETTE[i % PALETTE.length]}" radius={[4,4,0,0]}`);
1215
+ }
1216
+ lines.push("Include CartesianGrid (strokeDasharray='3 3'), Tooltip, and Legend.");
1217
+ break;
1218
+ }
1219
+ case "pie": {
1220
+ const m = w.metrics[0];
1221
+ const dim = w.groupBy[0];
1222
+ lines.push("Render a PieChart inside a ResponsiveContainer (height 300).");
1223
+ if (m && dim) {
1224
+ lines.push(`Pie dataKey="${m.name}" nameKey="${dim.name}". Label each slice with the dimension value and percentage.`);
1225
+ }
1226
+ lines.push(`Use Cell components to apply these fill colors in order: ${PALETTE.slice(0, 6).map((c) => `"${c}"`).join(", ")}.`);
1227
+ lines.push("Include Tooltip and Legend.");
1228
+ break;
1229
+ }
1230
+ case "table": {
1231
+ lines.push("Render a styled data table with:");
1232
+ lines.push(" - Sticky header row with a light gray background.");
1233
+ lines.push(" - Sortable columns (click header to toggle asc/desc).");
1234
+ lines.push(" - Zebra-striped rows for readability.");
1235
+ const allFields = [...w.groupBy, ...w.metrics];
1236
+ if (allFields.length > 0) {
1237
+ lines.push("Columns:");
1238
+ for (const f of allFields) {
1239
+ const align = f.type === "metric" ? "right-aligned, number-formatted" : "left-aligned";
1240
+ lines.push(` - "${f.displayName}" (key: "${f.name}") \u2014 ${align}`);
1241
+ }
1242
+ }
1243
+ break;
1244
+ }
1245
+ case "kpi": {
1246
+ const m = w.metrics[0];
1247
+ lines.push("Render a KPI card showing:");
1248
+ if (m) {
1249
+ lines.push(` - Large formatted number for "${m.displayName}" (key: "${m.name}").`);
1250
+ lines.push(` - Use Intl.NumberFormat with appropriate style (currency for revenue/dollar fields, decimal otherwise).`);
1251
+ lines.push(` - Subtitle label: "${m.displayName}".`);
1252
+ }
1253
+ if (w.comparisonMetric) {
1254
+ lines.push(` - Below the main number, show a comparison badge for "${w.comparisonMetric.displayName}" with a green up-arrow or red down-arrow indicating positive/negative change.`);
1255
+ }
1256
+ if (w.grain && w.groupBy.length > 0) {
1257
+ lines.push(` - Query with grain '${w.grain}' and take the most recent period's value. Optionally show a sparkline of the trend.`);
1258
+ } else {
1259
+ lines.push(" - Query without groupBy to get the aggregate total.");
1260
+ }
1261
+ break;
1262
+ }
1263
+ }
1264
+ return lines.join("\n");
1265
+ }
1266
+ function layoutInstructions(layout, widgets) {
1267
+ const kpiCount = widgets.filter((w) => w.type === "kpi").length;
1268
+ const chartCount = widgets.filter((w) => w.type !== "kpi" && w.type !== "table").length;
1269
+ const tableCount = widgets.filter((w) => w.type === "table").length;
1270
+ switch (layout) {
1271
+ case "single":
1272
+ return "Stack all widgets in a single column, full width. Use a flex column with gap 24px.";
1273
+ case "two-column":
1274
+ return "Use a CSS grid with `grid-template-columns: repeat(2, 1fr)` and gap 24px. KPI cards can be smaller \u2014 place up to 3 per row using `grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))` in a sub-grid. Tables span full width (`grid-column: 1 / -1`).";
1275
+ case "dashboard":
1276
+ return [
1277
+ "Use a dashboard layout with three zones:",
1278
+ kpiCount > 0 ? ` 1. **KPI row** at the top: ${kpiCount} KPI card(s) in a horizontal flex row with gap 16px, equal width.` : " 1. **KPI row**: (none configured \u2014 skip this zone).",
1279
+ chartCount > 0 ? ` 2. **Charts zone**: ${chartCount} chart(s) in a 2-column CSS grid (gap 24px). If only 1 chart, span full width.` : " 2. **Charts zone**: (none configured).",
1280
+ tableCount > 0 ? ` 3. **Table zone** at the bottom: full-width data table(s), stacked.` : " 3. **Table zone**: (none configured)."
1281
+ ].join("\n");
1282
+ }
1283
+ }
1284
+ function widgetLabel(type) {
1285
+ const labels = {
1286
+ line: "Line Chart",
1287
+ bar: "Bar Chart",
1288
+ area: "Area Chart",
1289
+ pie: "Pie / Donut Chart",
1290
+ table: "Data Table",
1291
+ kpi: "KPI Card"
1292
+ };
1293
+ return labels[type];
1294
+ }
1295
+ function sanitize(name) {
1296
+ return name.replace(/[^a-zA-Z0-9_]/g, "_");
1297
+ }
1298
+ var LOVABLE_CHAR_LIMIT = 5e4;
1299
+ function canOpenInLovable(prompt) {
1300
+ return prompt.length <= LOVABLE_CHAR_LIMIT;
1301
+ }
1302
+ function openInLovable(prompt) {
1303
+ const encoded = encodeURIComponent(prompt);
1304
+ const url = `https://lovable.dev/?autosubmit=true#prompt=${encoded}`;
1305
+ window.open(url, "_blank", "noopener");
1306
+ }
1081
1307
  function DataInspector({ children }) {
1082
1308
  return /* @__PURE__ */ jsxRuntime.jsxs(InspectableRegistry, { children: [
1083
1309
  /* @__PURE__ */ jsxRuntime.jsx(InspectorOverlay, {}),
@@ -1087,7 +1313,15 @@ function DataInspector({ children }) {
1087
1313
  function InspectorOverlay() {
1088
1314
  const registry = react.useContext(InspectableRegistryContext);
1089
1315
  if (!registry) return null;
1090
- const { inspectActive, setInspectActive, selectedId, setSelectedId, entries } = registry;
1316
+ const {
1317
+ inspectActive,
1318
+ setInspectActive,
1319
+ selectedId,
1320
+ setSelectedId,
1321
+ entries,
1322
+ modalTab,
1323
+ setModalTab
1324
+ } = registry;
1091
1325
  react.useEffect(() => {
1092
1326
  function handleKeyDown(e) {
1093
1327
  if (e.ctrlKey && e.shiftKey && e.key === "I") {
@@ -1098,38 +1332,60 @@ function InspectorOverlay() {
1098
1332
  window.addEventListener("keydown", handleKeyDown);
1099
1333
  return () => window.removeEventListener("keydown", handleKeyDown);
1100
1334
  }, [inspectActive, setInspectActive]);
1335
+ react.useEffect(() => {
1336
+ if (selectedId) setModalTab("migrate");
1337
+ }, [selectedId, setModalTab]);
1101
1338
  const selectedEntry = selectedId ? entries.get(selectedId) ?? null : null;
1339
+ const handleClose = react.useCallback(() => {
1340
+ setModalTab(null);
1341
+ setSelectedId(null);
1342
+ }, [setModalTab, setSelectedId]);
1343
+ const handleToggle = react.useCallback(() => {
1344
+ if (modalTab) {
1345
+ handleClose();
1346
+ setInspectActive(false);
1347
+ } else {
1348
+ setModalTab("build");
1349
+ setInspectActive(true);
1350
+ }
1351
+ }, [modalTab, handleClose, setModalTab, setInspectActive]);
1352
+ const handleTabChange = react.useCallback((tab) => {
1353
+ setModalTab(tab);
1354
+ if (tab === "build") setSelectedId(null);
1355
+ }, [setModalTab, setSelectedId]);
1102
1356
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1103
1357
  /* @__PURE__ */ jsxRuntime.jsx(
1104
1358
  ToggleButton,
1105
1359
  {
1106
1360
  active: inspectActive,
1361
+ modalOpen: modalTab !== null,
1107
1362
  count: entries.size,
1108
- onToggle: () => {
1109
- setInspectActive(!inspectActive);
1110
- if (inspectActive) setSelectedId(null);
1111
- }
1363
+ onToggle: handleToggle
1112
1364
  }
1113
1365
  ),
1114
- selectedEntry && /* @__PURE__ */ jsxRuntime.jsx(
1115
- InspectorModal,
1366
+ modalTab && /* @__PURE__ */ jsxRuntime.jsx(
1367
+ UnifiedModal,
1116
1368
  {
1369
+ tab: modalTab,
1370
+ onTabChange: handleTabChange,
1117
1371
  entry: selectedEntry,
1118
- onClose: () => setSelectedId(null)
1372
+ onClose: handleClose
1119
1373
  }
1120
1374
  )
1121
1375
  ] });
1122
1376
  }
1123
1377
  function ToggleButton({
1124
1378
  active,
1379
+ modalOpen,
1125
1380
  count,
1126
1381
  onToggle
1127
1382
  }) {
1383
+ const lit = active || modalOpen;
1128
1384
  return /* @__PURE__ */ jsxRuntime.jsxs(
1129
1385
  "button",
1130
1386
  {
1131
1387
  onClick: onToggle,
1132
- title: active ? "Exit inspect mode (Ctrl+Shift+I)" : "Enter inspect mode (Ctrl+Shift+I)",
1388
+ title: lit ? "Close data tools (Ctrl+Shift+I)" : "Open data tools (Ctrl+Shift+I)",
1133
1389
  style: {
1134
1390
  position: "fixed",
1135
1391
  bottom: 20,
@@ -1142,9 +1398,9 @@ function ToggleButton({
1142
1398
  fontSize: 13,
1143
1399
  fontWeight: 600,
1144
1400
  fontFamily: "system-ui, -apple-system, sans-serif",
1145
- color: active ? "#fff" : "#374151",
1146
- backgroundColor: active ? "#3b82f6" : "#fff",
1147
- border: `1px solid ${active ? "#2563eb" : "#d1d5db"}`,
1401
+ color: lit ? "#fff" : "#374151",
1402
+ backgroundColor: lit ? "#3b82f6" : "#fff",
1403
+ border: `1px solid ${lit ? "#2563eb" : "#d1d5db"}`,
1148
1404
  borderRadius: 999,
1149
1405
  cursor: "pointer",
1150
1406
  boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
@@ -1155,8 +1411,8 @@ function ToggleButton({
1155
1411
  /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
1156
1412
  /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
1157
1413
  ] }),
1158
- active ? "Inspecting" : "Inspect Data",
1159
- /* @__PURE__ */ jsxRuntime.jsx(
1414
+ lit ? "Active" : "Data Tools",
1415
+ count > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1160
1416
  "span",
1161
1417
  {
1162
1418
  style: {
@@ -1168,8 +1424,8 @@ function ToggleButton({
1168
1424
  fontSize: 11,
1169
1425
  fontWeight: 700,
1170
1426
  borderRadius: 999,
1171
- backgroundColor: active ? "rgba(255,255,255,0.25)" : "#e5e7eb",
1172
- color: active ? "#fff" : "#6b7280",
1427
+ backgroundColor: lit ? "rgba(255,255,255,0.25)" : "#e5e7eb",
1428
+ color: lit ? "#fff" : "#6b7280",
1173
1429
  padding: "0 5px"
1174
1430
  },
1175
1431
  children: count
@@ -1179,11 +1435,66 @@ function ToggleButton({
1179
1435
  }
1180
1436
  );
1181
1437
  }
1182
- function InspectorModal({
1438
+ function UnifiedModal({
1439
+ tab,
1440
+ onTabChange,
1183
1441
  entry,
1184
1442
  onClose
1443
+ }) {
1444
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1445
+ /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: onClose, style: backdropStyle }),
1446
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyle, children: [
1447
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalHeaderStyle, children: [
1448
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 0 }, children: [
1449
+ /* @__PURE__ */ jsxRuntime.jsx(TabButton, { label: "Migrate", active: tab === "migrate", onClick: () => onTabChange("migrate") }),
1450
+ /* @__PURE__ */ jsxRuntime.jsx(TabButton, { label: "Build", active: tab === "build", onClick: () => onTabChange("build") })
1451
+ ] }),
1452
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, style: closeBtnStyle, children: "\xD7" })
1453
+ ] }),
1454
+ tab === "migrate" ? /* @__PURE__ */ jsxRuntime.jsx(MigrateTab, { entry, onClose }) : /* @__PURE__ */ jsxRuntime.jsx(BuildTab, {})
1455
+ ] })
1456
+ ] });
1457
+ }
1458
+ function TabButton({ label, active, onClick }) {
1459
+ return /* @__PURE__ */ jsxRuntime.jsx(
1460
+ "button",
1461
+ {
1462
+ onClick,
1463
+ style: {
1464
+ padding: "8px 20px",
1465
+ fontSize: 13,
1466
+ fontWeight: 600,
1467
+ fontFamily: "system-ui, -apple-system, sans-serif",
1468
+ color: active ? "#3b82f6" : "#6b7280",
1469
+ backgroundColor: "transparent",
1470
+ border: "none",
1471
+ borderBottom: active ? "2px solid #3b82f6" : "2px solid transparent",
1472
+ cursor: "pointer",
1473
+ transition: "all 0.15s ease"
1474
+ },
1475
+ children: label
1476
+ }
1477
+ );
1478
+ }
1479
+ function MigrateTab({
1480
+ entry,
1481
+ onClose: _onClose
1185
1482
  }) {
1186
1483
  const { fields: catalog, error: catalogError, isLoading: catalogLoading } = chunkT2C43AAL_cjs.useMetrics();
1484
+ if (!entry) {
1485
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: 40 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", color: "#9ca3af" }, children: [
1486
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 14, fontWeight: 500, marginBottom: 8 }, children: "No component selected" }),
1487
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12 }, children: "Enable inspect mode (Ctrl+Shift+I) then click on a data component to map it to the semantic layer." })
1488
+ ] }) });
1489
+ }
1490
+ return /* @__PURE__ */ jsxRuntime.jsx(MigrateTabContent, { entry, catalog, catalogError, catalogLoading });
1491
+ }
1492
+ function MigrateTabContent({
1493
+ entry,
1494
+ catalog,
1495
+ catalogError,
1496
+ catalogLoading
1497
+ }) {
1187
1498
  const analysis = react.useMemo(
1188
1499
  () => analyzeChartData(entry.columns, entry.sampleValues),
1189
1500
  [entry.columns, entry.sampleValues]
@@ -1231,204 +1542,601 @@ function InspectorModal({
1231
1542
  originalMetricColumns: analysis.metricColumns
1232
1543
  };
1233
1544
  const prompt = generateMigrationPrompt(migrationConfig);
1234
- try {
1235
- await navigator.clipboard.writeText(prompt);
1236
- setCopied(true);
1237
- setTimeout(() => setCopied(false), 2e3);
1238
- } catch {
1239
- const ta = document.createElement("textarea");
1240
- ta.value = prompt;
1241
- document.body.appendChild(ta);
1242
- ta.select();
1243
- document.execCommand("copy");
1244
- document.body.removeChild(ta);
1245
- setCopied(true);
1246
- setTimeout(() => setCopied(false), 2e3);
1247
- }
1545
+ await copyToClipboard(prompt);
1546
+ setCopied(true);
1547
+ setTimeout(() => setCopied(false), 2e3);
1248
1548
  }, [entry, dimMappings, selectedMetrics, grain, hasTimeDim, analysis.metricColumns]);
1249
1549
  const canGenerate = selectedMetrics.length > 0 || Array.from(dimMappings.values()).some(Boolean);
1250
1550
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1251
- /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: onClose, style: backdropStyle }),
1252
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyle, children: [
1253
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalHeaderStyle, children: [
1254
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1255
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 700, color: "#111827" }, children: entry.label }),
1256
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 12, color: "#6b7280", marginTop: 2 }, children: [
1257
- "Source: ",
1258
- entry.currentSource
1259
- ] })
1260
- ] }),
1261
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, style: closeBtnStyle, children: "\xD7" })
1551
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { overflow: "auto", flex: 1 }, children: [
1552
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1553
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 15, fontWeight: 700, color: "#111827" }, children: entry.label }),
1554
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 12, color: "#6b7280", marginTop: 2 }, children: [
1555
+ "Source: ",
1556
+ entry.currentSource
1557
+ ] })
1558
+ ] }),
1559
+ entry.sampleValues.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1560
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Current Data Preview" }),
1561
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: previewTableStyle, children: [
1562
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { style: previewThStyle, children: col }, col)) }) }),
1563
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: entry.sampleValues.map((row, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: previewTdStyle, children: row[col] === null || row[col] === void 0 ? "\u2014" : String(row[col]) }, col)) }, i)) })
1564
+ ] }) }),
1565
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "#9ca3af" }, children: [
1566
+ "Detected: ",
1567
+ analysis.dimensionColumns.length,
1568
+ " dimension column",
1569
+ analysis.dimensionColumns.length !== 1 ? "s" : "",
1570
+ " (",
1571
+ analysis.dimensionColumns.join(", ") || "none",
1572
+ ")",
1573
+ " \xB7 ",
1574
+ analysis.metricColumns.length,
1575
+ " value column",
1576
+ analysis.metricColumns.length !== 1 ? "s" : "",
1577
+ " (",
1578
+ analysis.metricColumns.join(", ") || "none",
1579
+ ")"
1580
+ ] })
1262
1581
  ] }),
1263
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { overflow: "auto", flex: 1 }, children: [
1264
- entry.sampleValues.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1265
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Current Data Preview" }),
1266
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: previewTableStyle, children: [
1267
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { style: previewThStyle, children: col }, col)) }) }),
1268
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: entry.sampleValues.map((row, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: previewTdStyle, children: row[col] === null || row[col] === void 0 ? "\u2014" : String(row[col]) }, col)) }, i)) })
1582
+ catalog.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...sectionStyle, textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#9ca3af" }, children: catalogLoading ? "Loading catalog..." : catalogError ? `Error loading catalog: ${catalogError}` : "No semantic layer catalog available. Connect to the gateway to enable smart matching." }) }),
1583
+ catalog.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1584
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Dimensions (Group By)" }),
1585
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: "What categories define the rows?" }),
1586
+ analysis.dimensionColumns.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#9ca3af", fontStyle: "italic" }, children: "No dimension columns detected." }) : /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13 }, children: [
1587
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1588
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Current Column" }),
1589
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Sample Values" }),
1590
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Semantic Layer Dimension" })
1269
1591
  ] }) }),
1270
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "#9ca3af" }, children: [
1271
- "Detected: ",
1272
- analysis.dimensionColumns.length,
1273
- " dimension column",
1274
- analysis.dimensionColumns.length !== 1 ? "s" : "",
1275
- " (",
1276
- analysis.dimensionColumns.join(", ") || "none",
1277
- ")",
1278
- " \xB7 ",
1279
- analysis.metricColumns.length,
1280
- " value column",
1281
- analysis.metricColumns.length !== 1 ? "s" : "",
1282
- " (",
1283
- analysis.metricColumns.join(", ") || "none",
1592
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: analysis.dimensionColumns.map((col) => {
1593
+ const sampleVals = entry.sampleValues.map((row) => row[col]).filter((v) => v !== null && v !== void 0).slice(0, 4).map(String);
1594
+ return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1595
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsxRuntime.jsx("code", { style: { fontSize: 12, color: "#6366f1", fontFamily: "monospace" }, children: col }) }),
1596
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { style: { ...mappingTdStyle, fontSize: 11, color: "#9ca3af" }, children: [
1597
+ sampleVals.join(", "),
1598
+ sampleVals.length >= 4 ? ", ..." : ""
1599
+ ] }),
1600
+ /* @__PURE__ */ jsxRuntime.jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsxRuntime.jsxs(
1601
+ "select",
1602
+ {
1603
+ value: dimMappings.get(col)?.name ?? "",
1604
+ onChange: (e) => {
1605
+ const f = dimensions.find((d) => d.name === e.target.value) ?? null;
1606
+ handleDimChange(col, f);
1607
+ },
1608
+ style: selectStyle2,
1609
+ children: [
1610
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "-- select dimension --" }),
1611
+ dimensions.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: f.name, children: [
1612
+ f.displayName,
1613
+ " (",
1614
+ f.type,
1615
+ ") \\u2014 ",
1616
+ f.name
1617
+ ] }, f.name))
1618
+ ]
1619
+ }
1620
+ ) })
1621
+ ] }, col);
1622
+ }) })
1623
+ ] }),
1624
+ hasTimeDim && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 10, display: "flex", alignItems: "center", gap: 8 }, children: [
1625
+ /* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 12, fontWeight: 600, color: "#374151" }, children: "Time Grain:" }),
1626
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { value: grain, onChange: (e) => setGrain(e.target.value), style: { ...selectStyle2, width: "auto" }, children: [
1627
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: "Day" }),
1628
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: "Week" }),
1629
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "month", children: "Month" }),
1630
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "quarter", children: "Quarter" }),
1631
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "year", children: "Year" })
1632
+ ] })
1633
+ ] })
1634
+ ] }),
1635
+ catalog.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1636
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Metrics (Values)" }),
1637
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: [
1638
+ "Which metrics should be queried?",
1639
+ analysis.metricColumns.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1640
+ " (replaces: ",
1641
+ analysis.metricColumns.join(", "),
1284
1642
  ")"
1285
1643
  ] })
1286
1644
  ] }),
1287
- catalog.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...sectionStyle, textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#9ca3af" }, children: catalogLoading ? "Loading catalog..." : catalogError ? `Error loading catalog: ${catalogError}` : "No semantic layer catalog available. Connect to the gateway to enable smart matching." }) }),
1288
- catalog.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1289
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Dimensions (Group By)" }),
1290
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: "What categories define the rows? (X-axis, grouping)" }),
1291
- analysis.dimensionColumns.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#9ca3af", fontStyle: "italic" }, children: "No dimension columns detected in the data." }) : /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13 }, children: [
1292
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1293
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Current Column" }),
1294
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Sample Values" }),
1295
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Semantic Layer Dimension" })
1296
- ] }) }),
1297
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: analysis.dimensionColumns.map((col) => {
1298
- const sampleVals = entry.sampleValues.map((row) => row[col]).filter((v) => v !== null && v !== void 0).slice(0, 4).map(String);
1299
- return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1300
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsxRuntime.jsx("code", { style: { fontSize: 12, color: "#6366f1", fontFamily: "monospace" }, children: col }) }),
1301
- /* @__PURE__ */ jsxRuntime.jsxs("td", { style: { ...mappingTdStyle, fontSize: 11, color: "#9ca3af" }, children: [
1302
- sampleVals.join(", "),
1303
- sampleVals.length >= 4 ? ", ..." : ""
1304
- ] }),
1305
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsxRuntime.jsxs(
1306
- "select",
1307
- {
1308
- value: dimMappings.get(col)?.name ?? "",
1309
- onChange: (e) => {
1310
- const field = dimensions.find((f) => f.name === e.target.value) ?? null;
1311
- handleDimChange(col, field);
1312
- },
1313
- style: selectStyle2,
1314
- children: [
1315
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "-- select dimension --" }),
1316
- dimensions.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: f.name, children: [
1317
- f.displayName,
1318
- " (",
1319
- f.type,
1320
- ") \u2014 ",
1321
- f.name
1322
- ] }, f.name))
1323
- ]
1324
- }
1325
- ) })
1326
- ] }, col);
1327
- }) })
1328
- ] }),
1329
- hasTimeDim && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 10, display: "flex", alignItems: "center", gap: 8 }, children: [
1330
- /* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 12, fontWeight: 600, color: "#374151" }, children: "Time Grain:" }),
1331
- /* @__PURE__ */ jsxRuntime.jsxs(
1332
- "select",
1333
- {
1334
- value: grain,
1335
- onChange: (e) => setGrain(e.target.value),
1336
- style: { ...selectStyle2, width: "auto" },
1337
- children: [
1338
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: "Day" }),
1339
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: "Week" }),
1340
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "month", children: "Month" }),
1341
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "quarter", children: "Quarter" }),
1342
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "year", children: "Year" })
1343
- ]
1344
- }
1345
- )
1645
+ metrics.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#9ca3af", fontStyle: "italic" }, children: "No metrics available." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 4, maxHeight: 200, overflow: "auto" }, children: metrics.map((m) => {
1646
+ const isSel = selectedMetrics.some((s) => s.name === m.name);
1647
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 8, padding: "4px 8px", borderRadius: 4, cursor: "pointer", backgroundColor: isSel ? "#eff6ff" : "transparent", border: isSel ? "1px solid #bfdbfe" : "1px solid transparent", fontSize: 12 }, children: [
1648
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: isSel, onChange: () => handleToggleMetric(m), style: { margin: 0 } }),
1649
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500, color: "#111827" }, children: m.displayName }),
1650
+ /* @__PURE__ */ jsxRuntime.jsx("code", { style: { fontSize: 10, color: "#6366f1", fontFamily: "monospace" }, children: m.name }),
1651
+ m.category && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: "auto", fontSize: 10, color: "#9ca3af" }, children: m.category })
1652
+ ] }, m.name);
1653
+ }) }),
1654
+ selectedMetrics.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "#6b7280" }, children: [
1655
+ "Selected: ",
1656
+ selectedMetrics.map((m) => m.displayName).join(", ")
1657
+ ] })
1658
+ ] }),
1659
+ canGenerate && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1660
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Query Preview" }),
1661
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { style: codePreviewStyle, children: `useSemanticQuery({${selectedMetrics.length > 0 ? `
1662
+ metrics: [${selectedMetrics.map((m) => `'${m.name}'`).join(", ")}],` : ""}${Array.from(dimMappings.values()).some(Boolean) ? `
1663
+ groupBy: [${Array.from(dimMappings.values()).filter(Boolean).map((f) => `'${f.name}'`).join(", ")}],` : ""}${hasTimeDim && grain ? `
1664
+ grain: '${grain}',` : ""}
1665
+ })` })
1666
+ ] })
1667
+ ] }),
1668
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "12px 20px", borderTop: "1px solid #e5e7eb" }, children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleGenerate, disabled: !canGenerate, style: { ...primaryBtnStyle, width: "100%", opacity: canGenerate ? 1 : 0.5, cursor: canGenerate ? "pointer" : "not-allowed" }, children: copied ? "Copied to clipboard!" : "Generate Migration Prompt" }) })
1669
+ ] });
1670
+ }
1671
+ var _nextWidgetId = 1;
1672
+ function nextWidgetId() {
1673
+ return `w${_nextWidgetId++}`;
1674
+ }
1675
+ var WIDGET_TYPES = [
1676
+ { type: "kpi", label: "KPI", icon: "#" },
1677
+ { type: "line", label: "Line", icon: "\u2571" },
1678
+ { type: "bar", label: "Bar", icon: "\u2581\u2583\u2585" },
1679
+ { type: "area", label: "Area", icon: "\u25E2" },
1680
+ { type: "pie", label: "Pie", icon: "\u25D4" },
1681
+ { type: "table", label: "Table", icon: "\u2637" }
1682
+ ];
1683
+ function BuildTab() {
1684
+ const { fields: catalog, isLoading: catalogLoading } = chunkT2C43AAL_cjs.useMetrics();
1685
+ const allMetrics = react.useMemo(() => catalog.filter((f) => f.type === "metric"), [catalog]);
1686
+ const allDimensions = react.useMemo(() => catalog.filter((f) => f.type === "dimension" || f.type === "time_dimension"), [catalog]);
1687
+ const [dashboardTitle, setDashboardTitle] = react.useState("Dashboard");
1688
+ const [layout, setLayout] = react.useState("dashboard");
1689
+ const [widgets, setWidgets] = react.useState([]);
1690
+ const [sharedFilters, setSharedFilters] = react.useState([]);
1691
+ const [catalogSearch, setCatalogSearch] = react.useState("");
1692
+ const [copied, setCopied] = react.useState(false);
1693
+ const [lovableSent, setLovableSent] = react.useState(false);
1694
+ const [previewOpen, setPreviewOpen] = react.useState(false);
1695
+ const addWidget = react.useCallback((type) => {
1696
+ setWidgets((prev) => [...prev, {
1697
+ id: nextWidgetId(),
1698
+ title: `${WIDGET_TYPES.find((t) => t.type === type)?.label ?? "Widget"} ${prev.length + 1}`,
1699
+ type,
1700
+ metrics: [],
1701
+ groupBy: [],
1702
+ grain: null
1703
+ }]);
1704
+ }, []);
1705
+ const removeWidget = react.useCallback((id) => {
1706
+ setWidgets((prev) => prev.filter((w) => w.id !== id));
1707
+ }, []);
1708
+ const updateWidget = react.useCallback((id, patch) => {
1709
+ setWidgets((prev) => prev.map((w) => w.id === id ? { ...w, ...patch } : w));
1710
+ }, []);
1711
+ const addFilter = react.useCallback((dim) => {
1712
+ if (sharedFilters.some((f) => f.dimension.name === dim.name)) return;
1713
+ setSharedFilters((prev) => [...prev, { dimension: dim, label: dim.displayName }]);
1714
+ }, [sharedFilters]);
1715
+ const removeFilter = react.useCallback((name) => {
1716
+ setSharedFilters((prev) => prev.filter((f) => f.dimension.name !== name));
1717
+ }, []);
1718
+ const addFieldToWidget = react.useCallback((widgetId, field) => {
1719
+ setWidgets((prev) => prev.map((w) => {
1720
+ if (w.id !== widgetId) return w;
1721
+ if (field.type === "metric") {
1722
+ if (w.metrics.some((m) => m.name === field.name)) return w;
1723
+ return { ...w, metrics: [...w.metrics, field] };
1724
+ } else {
1725
+ if (w.groupBy.some((d) => d.name === field.name)) return w;
1726
+ return { ...w, groupBy: [...w.groupBy, field] };
1727
+ }
1728
+ }));
1729
+ }, []);
1730
+ const removeFieldFromWidget = react.useCallback((widgetId, fieldName, role) => {
1731
+ setWidgets((prev) => prev.map((w) => {
1732
+ if (w.id !== widgetId) return w;
1733
+ if (role === "metric") return { ...w, metrics: w.metrics.filter((m) => m.name !== fieldName) };
1734
+ return { ...w, groupBy: w.groupBy.filter((d) => d.name !== fieldName) };
1735
+ }));
1736
+ }, []);
1737
+ const reportConfig = react.useMemo(() => ({
1738
+ dashboardTitle,
1739
+ layout,
1740
+ widgets,
1741
+ sharedFilters
1742
+ }), [dashboardTitle, layout, widgets, sharedFilters]);
1743
+ const prompt = react.useMemo(() => generateReportPrompt(reportConfig), [reportConfig]);
1744
+ const canGenerate = widgets.length > 0 && widgets.some((w) => w.metrics.length > 0);
1745
+ const canLovable = canGenerate && canOpenInLovable(prompt);
1746
+ const handleCopy = react.useCallback(async () => {
1747
+ await copyToClipboard(prompt);
1748
+ setCopied(true);
1749
+ setTimeout(() => setCopied(false), 2e3);
1750
+ }, [prompt]);
1751
+ const handleLovable = react.useCallback(() => {
1752
+ openInLovable(prompt);
1753
+ setLovableSent(true);
1754
+ setTimeout(() => setLovableSent(false), 3e3);
1755
+ }, [prompt]);
1756
+ const filteredMetrics = react.useMemo(() => {
1757
+ const q = catalogSearch.toLowerCase();
1758
+ if (!q) return allMetrics;
1759
+ return allMetrics.filter((f) => f.displayName.toLowerCase().includes(q) || f.name.toLowerCase().includes(q) || f.description.toLowerCase().includes(q));
1760
+ }, [allMetrics, catalogSearch]);
1761
+ const filteredDimensions = react.useMemo(() => {
1762
+ const q = catalogSearch.toLowerCase();
1763
+ if (!q) return allDimensions;
1764
+ return allDimensions.filter((f) => f.displayName.toLowerCase().includes(q) || f.name.toLowerCase().includes(q) || f.description.toLowerCase().includes(q));
1765
+ }, [allDimensions, catalogSearch]);
1766
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1767
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { overflow: "auto", flex: 1, display: "flex", flexDirection: "column" }, children: [
1768
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...sectionStyle, display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }, children: [
1769
+ /* @__PURE__ */ jsxRuntime.jsx(
1770
+ "input",
1771
+ {
1772
+ value: dashboardTitle,
1773
+ onChange: (e) => setDashboardTitle(e.target.value),
1774
+ placeholder: "Dashboard title",
1775
+ style: { ...inputStyle2, flex: 1, minWidth: 160, fontSize: 15, fontWeight: 700 }
1776
+ }
1777
+ ),
1778
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 4 }, children: ["single", "two-column", "dashboard"].map((l) => /* @__PURE__ */ jsxRuntime.jsx(
1779
+ "button",
1780
+ {
1781
+ onClick: () => setLayout(l),
1782
+ style: {
1783
+ padding: "4px 10px",
1784
+ fontSize: 11,
1785
+ fontWeight: 600,
1786
+ fontFamily: "system-ui, -apple-system, sans-serif",
1787
+ borderRadius: 4,
1788
+ border: layout === l ? "1px solid #3b82f6" : "1px solid #d1d5db",
1789
+ backgroundColor: layout === l ? "#eff6ff" : "#fff",
1790
+ color: layout === l ? "#3b82f6" : "#6b7280",
1791
+ cursor: "pointer"
1792
+ },
1793
+ children: l === "single" ? "1-Col" : l === "two-column" ? "2-Col" : "Dashboard"
1794
+ },
1795
+ l
1796
+ )) })
1797
+ ] }),
1798
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flex: 1, minHeight: 0 }, children: [
1799
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: 220, borderRight: "1px solid #e5e7eb", overflow: "auto", flexShrink: 0 }, children: [
1800
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "8px 12px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1801
+ "input",
1802
+ {
1803
+ value: catalogSearch,
1804
+ onChange: (e) => setCatalogSearch(e.target.value),
1805
+ placeholder: "Search fields...",
1806
+ style: { ...inputStyle2, width: "100%", fontSize: 12 }
1807
+ }
1808
+ ) }),
1809
+ catalogLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 12, fontSize: 12, color: "#9ca3af" }, children: "Loading catalog..." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1810
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "4px 12px" }, children: [
1811
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...sectionTitleStyle, marginBottom: 4 }, children: [
1812
+ "Metrics (",
1813
+ filteredMetrics.length,
1814
+ ")"
1815
+ ] }),
1816
+ filteredMetrics.map((f) => /* @__PURE__ */ jsxRuntime.jsx(CatalogChip, { field: f, widgets, onAddToWidget: addFieldToWidget }, f.name)),
1817
+ filteredMetrics.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", padding: "4px 0" }, children: "None found" })
1818
+ ] }),
1819
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "4px 12px" }, children: [
1820
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...sectionTitleStyle, marginBottom: 4 }, children: [
1821
+ "Dimensions (",
1822
+ filteredDimensions.length,
1823
+ ")"
1824
+ ] }),
1825
+ filteredDimensions.map((f) => /* @__PURE__ */ jsxRuntime.jsx(CatalogChip, { field: f, widgets, onAddToWidget: addFieldToWidget, onAddAsFilter: addFilter }, f.name)),
1826
+ filteredDimensions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", padding: "4px 0" }, children: "None found" })
1827
+ ] })
1346
1828
  ] })
1347
1829
  ] }),
1348
- catalog.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1349
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Metrics (Values)" }),
1350
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: [
1351
- "Which metrics should be queried? These become the series/values in the chart.",
1352
- analysis.metricColumns.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1353
- " (replaces: ",
1354
- analysis.metricColumns.join(", "),
1355
- ")"
1356
- ] })
1830
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, overflow: "auto", padding: "12px 16px" }, children: widgets.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", padding: "40px 20px", color: "#9ca3af" }, children: [
1831
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 14, fontWeight: 500, marginBottom: 12 }, children: "Compose your dashboard" }),
1832
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, marginBottom: 20 }, children: "Add widgets below, then pick metrics and dimensions from the catalog on the left." }),
1833
+ /* @__PURE__ */ jsxRuntime.jsx(AddWidgetBar, { onAdd: addWidget })
1834
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1835
+ widgets.map((w) => /* @__PURE__ */ jsxRuntime.jsx(
1836
+ WidgetCard,
1837
+ {
1838
+ widget: w,
1839
+ onUpdate: updateWidget,
1840
+ onRemove: removeWidget,
1841
+ onRemoveField: removeFieldFromWidget,
1842
+ allDimensions
1843
+ },
1844
+ w.id
1845
+ )),
1846
+ /* @__PURE__ */ jsxRuntime.jsx(AddWidgetBar, { onAdd: addWidget }),
1847
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 16, padding: "12px 0", borderTop: "1px solid #f3f4f6" }, children: [
1848
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Shared Filters" }),
1849
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: "Dimensions that filter all widgets together. Add from the catalog sidebar." }),
1850
+ sharedFilters.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#d1d5db", fontStyle: "italic" }, children: "None added yet" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: sharedFilters.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: chipStyle, children: [
1851
+ f.label,
1852
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => removeFilter(f.dimension.name), style: chipRemoveStyle, children: "\xD7" })
1853
+ ] }, f.dimension.name)) })
1357
1854
  ] }),
1358
- metrics.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#9ca3af", fontStyle: "italic" }, children: "No metrics available in the catalog." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 4, maxHeight: 200, overflow: "auto" }, children: metrics.map((m) => {
1359
- const isSelected = selectedMetrics.some((s) => s.name === m.name);
1360
- return /* @__PURE__ */ jsxRuntime.jsxs(
1361
- "label",
1855
+ canGenerate && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 12 }, children: [
1856
+ /* @__PURE__ */ jsxRuntime.jsxs(
1857
+ "button",
1362
1858
  {
1363
- style: {
1364
- display: "flex",
1365
- alignItems: "center",
1366
- gap: 8,
1367
- padding: "4px 8px",
1368
- borderRadius: 4,
1369
- cursor: "pointer",
1370
- backgroundColor: isSelected ? "#eff6ff" : "transparent",
1371
- border: isSelected ? "1px solid #bfdbfe" : "1px solid transparent",
1372
- fontSize: 12
1373
- },
1859
+ onClick: () => setPreviewOpen(!previewOpen),
1860
+ style: { fontSize: 11, fontWeight: 600, color: "#6b7280", background: "none", border: "none", cursor: "pointer", padding: 0, fontFamily: "system-ui, -apple-system, sans-serif" },
1374
1861
  children: [
1375
- /* @__PURE__ */ jsxRuntime.jsx(
1376
- "input",
1377
- {
1378
- type: "checkbox",
1379
- checked: isSelected,
1380
- onChange: () => handleToggleMetric(m),
1381
- style: { margin: 0 }
1382
- }
1383
- ),
1384
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500, color: "#111827" }, children: m.displayName }),
1385
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: { fontSize: 10, color: "#6366f1", fontFamily: "monospace" }, children: m.name }),
1386
- m.category && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: "auto", fontSize: 10, color: "#9ca3af" }, children: m.category })
1862
+ previewOpen ? "\u25BC" : "\u25B6",
1863
+ " Prompt Preview"
1387
1864
  ]
1388
- },
1389
- m.name
1390
- );
1391
- }) }),
1392
- selectedMetrics.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "#6b7280" }, children: [
1393
- "Selected: ",
1394
- selectedMetrics.map((m) => m.displayName).join(", ")
1865
+ }
1866
+ ),
1867
+ previewOpen && /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { ...codePreviewStyle, marginTop: 8, maxHeight: 200, overflow: "auto", fontSize: 10 }, children: prompt })
1395
1868
  ] })
1396
- ] }),
1397
- canGenerate && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1398
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Query Preview" }),
1399
- /* @__PURE__ */ jsxRuntime.jsx("pre", { style: {
1400
- fontSize: 11,
1401
- fontFamily: "monospace",
1402
- backgroundColor: "#f9fafb",
1403
- border: "1px solid #e5e7eb",
1404
- borderRadius: 6,
1405
- padding: "8px 12px",
1406
- whiteSpace: "pre-wrap",
1407
- color: "#374151",
1408
- margin: 0
1409
- }, children: `useSemanticQuery({${selectedMetrics.length > 0 ? `
1410
- metrics: [${selectedMetrics.map((m) => `'${m.name}'`).join(", ")}],` : ""}${Array.from(dimMappings.values()).some(Boolean) ? `
1411
- groupBy: [${Array.from(dimMappings.values()).filter(Boolean).map((f) => `'${f.name}'`).join(", ")}],` : ""}${hasTimeDim && grain ? `
1412
- grain: '${grain}',` : ""}
1413
- })` })
1414
- ] })
1415
- ] }),
1416
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "12px 20px", borderTop: "1px solid #e5e7eb" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1869
+ ] }) })
1870
+ ] })
1871
+ ] }),
1872
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "12px 20px", borderTop: "1px solid #e5e7eb", display: "flex", gap: 10 }, children: [
1873
+ /* @__PURE__ */ jsxRuntime.jsx(
1417
1874
  "button",
1418
1875
  {
1419
- onClick: handleGenerate,
1876
+ onClick: handleCopy,
1420
1877
  disabled: !canGenerate,
1421
1878
  style: {
1422
- ...generateBtnStyle,
1879
+ ...secondaryBtnStyle,
1880
+ flex: 1,
1423
1881
  opacity: canGenerate ? 1 : 0.5,
1424
1882
  cursor: canGenerate ? "pointer" : "not-allowed"
1425
1883
  },
1426
- children: copied ? "Copied to clipboard!" : "Generate Migration Prompt"
1884
+ children: copied ? "Copied!" : "Copy Prompt"
1427
1885
  }
1428
- ) })
1886
+ ),
1887
+ /* @__PURE__ */ jsxRuntime.jsxs(
1888
+ "button",
1889
+ {
1890
+ onClick: handleLovable,
1891
+ disabled: !canLovable,
1892
+ title: canGenerate && !canLovable ? "Prompt exceeds 50,000 character limit for direct send" : void 0,
1893
+ style: {
1894
+ ...primaryBtnStyle,
1895
+ flex: 1.5,
1896
+ opacity: canLovable ? 1 : 0.4,
1897
+ cursor: canLovable ? "pointer" : "not-allowed",
1898
+ display: "flex",
1899
+ alignItems: "center",
1900
+ justifyContent: "center",
1901
+ gap: 6
1902
+ },
1903
+ children: [
1904
+ lovableSent ? "Opening Lovable..." : "Build in Lovable",
1905
+ !lovableSent && /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1906
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 17L17 7" }),
1907
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 7h10v10" })
1908
+ ] })
1909
+ ]
1910
+ }
1911
+ )
1912
+ ] })
1913
+ ] });
1914
+ }
1915
+ function CatalogChip({
1916
+ field,
1917
+ widgets,
1918
+ onAddToWidget,
1919
+ onAddAsFilter
1920
+ }) {
1921
+ const [menuOpen, setMenuOpen] = react.useState(false);
1922
+ const typeIcon = field.type === "metric" ? "\u2581" : field.type === "time_dimension" ? "\u25F7" : "\u2022";
1923
+ const typeColor = field.type === "metric" ? "#10b981" : field.type === "time_dimension" ? "#f59e0b" : "#6366f1";
1924
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
1925
+ /* @__PURE__ */ jsxRuntime.jsxs(
1926
+ "button",
1927
+ {
1928
+ onClick: () => {
1929
+ if (widgets.length === 1) {
1930
+ onAddToWidget(widgets[0].id, field);
1931
+ } else if (widgets.length > 1) {
1932
+ setMenuOpen(!menuOpen);
1933
+ }
1934
+ },
1935
+ title: `${field.displayName}
1936
+ ${field.description}`,
1937
+ style: {
1938
+ display: "flex",
1939
+ alignItems: "center",
1940
+ gap: 4,
1941
+ width: "100%",
1942
+ textAlign: "left",
1943
+ padding: "3px 6px",
1944
+ fontSize: 11,
1945
+ fontFamily: "system-ui, -apple-system, sans-serif",
1946
+ color: "#374151",
1947
+ backgroundColor: "transparent",
1948
+ border: "none",
1949
+ borderRadius: 3,
1950
+ cursor: widgets.length > 0 ? "pointer" : "default",
1951
+ opacity: widgets.length > 0 ? 1 : 0.6
1952
+ },
1953
+ onMouseEnter: (e) => {
1954
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
1955
+ },
1956
+ onMouseLeave: (e) => {
1957
+ e.currentTarget.style.backgroundColor = "transparent";
1958
+ },
1959
+ children: [
1960
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: typeColor, fontSize: 10, flexShrink: 0 }, children: typeIcon }),
1961
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: field.displayName })
1962
+ ]
1963
+ }
1964
+ ),
1965
+ menuOpen && widgets.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "absolute", left: 0, top: "100%", zIndex: 10, backgroundColor: "#fff", border: "1px solid #e5e7eb", borderRadius: 6, boxShadow: "0 4px 12px rgba(0,0,0,0.1)", minWidth: 160, padding: "4px 0" }, children: [
1966
+ widgets.map((w) => /* @__PURE__ */ jsxRuntime.jsx(
1967
+ "button",
1968
+ {
1969
+ onClick: () => {
1970
+ onAddToWidget(w.id, field);
1971
+ setMenuOpen(false);
1972
+ },
1973
+ style: { display: "block", width: "100%", textAlign: "left", padding: "4px 10px", fontSize: 11, fontFamily: "system-ui, -apple-system, sans-serif", border: "none", backgroundColor: "transparent", cursor: "pointer", color: "#374151" },
1974
+ onMouseEnter: (e) => {
1975
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
1976
+ },
1977
+ onMouseLeave: (e) => {
1978
+ e.currentTarget.style.backgroundColor = "transparent";
1979
+ },
1980
+ children: w.title
1981
+ },
1982
+ w.id
1983
+ )),
1984
+ onAddAsFilter && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1985
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderTop: "1px solid #e5e7eb", margin: "4px 0" } }),
1986
+ /* @__PURE__ */ jsxRuntime.jsx(
1987
+ "button",
1988
+ {
1989
+ onClick: () => {
1990
+ onAddAsFilter(field);
1991
+ setMenuOpen(false);
1992
+ },
1993
+ style: { display: "block", width: "100%", textAlign: "left", padding: "4px 10px", fontSize: 11, fontFamily: "system-ui, -apple-system, sans-serif", border: "none", backgroundColor: "transparent", cursor: "pointer", color: "#6b7280", fontStyle: "italic" },
1994
+ onMouseEnter: (e) => {
1995
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
1996
+ },
1997
+ onMouseLeave: (e) => {
1998
+ e.currentTarget.style.backgroundColor = "transparent";
1999
+ },
2000
+ children: "+ Add as shared filter"
2001
+ }
2002
+ )
2003
+ ] })
1429
2004
  ] })
1430
2005
  ] });
1431
2006
  }
2007
+ function AddWidgetBar({ onAdd }) {
2008
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 6, justifyContent: "center", padding: "8px 0", flexWrap: "wrap" }, children: WIDGET_TYPES.map((wt) => /* @__PURE__ */ jsxRuntime.jsxs(
2009
+ "button",
2010
+ {
2011
+ onClick: () => onAdd(wt.type),
2012
+ style: {
2013
+ display: "flex",
2014
+ flexDirection: "column",
2015
+ alignItems: "center",
2016
+ gap: 2,
2017
+ padding: "6px 12px",
2018
+ fontSize: 10,
2019
+ fontWeight: 600,
2020
+ fontFamily: "system-ui, -apple-system, sans-serif",
2021
+ color: "#6b7280",
2022
+ backgroundColor: "#fff",
2023
+ border: "1px dashed #d1d5db",
2024
+ borderRadius: 6,
2025
+ cursor: "pointer",
2026
+ transition: "all 0.1s",
2027
+ minWidth: 56
2028
+ },
2029
+ onMouseEnter: (e) => {
2030
+ const el = e.currentTarget;
2031
+ el.style.borderColor = "#3b82f6";
2032
+ el.style.color = "#3b82f6";
2033
+ },
2034
+ onMouseLeave: (e) => {
2035
+ const el = e.currentTarget;
2036
+ el.style.borderColor = "#d1d5db";
2037
+ el.style.color = "#6b7280";
2038
+ },
2039
+ children: [
2040
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 16, lineHeight: 1 }, children: wt.icon }),
2041
+ wt.label
2042
+ ]
2043
+ },
2044
+ wt.type
2045
+ )) });
2046
+ }
2047
+ function WidgetCard({
2048
+ widget,
2049
+ onUpdate,
2050
+ onRemove,
2051
+ onRemoveField,
2052
+ allDimensions
2053
+ }) {
2054
+ const [collapsed, setCollapsed] = react.useState(false);
2055
+ const hasTimeDim = widget.groupBy.some((d) => d.type === "time_dimension");
2056
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: "1px solid #e5e7eb", borderRadius: 8, marginBottom: 10, backgroundColor: "#fff" }, children: [
2057
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "8px 12px", borderBottom: collapsed ? "none" : "1px solid #f3f4f6" }, children: [
2058
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setCollapsed(!collapsed), style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontSize: 10, color: "#9ca3af" }, children: collapsed ? "\u25B6" : "\u25BC" }),
2059
+ /* @__PURE__ */ jsxRuntime.jsx(
2060
+ "input",
2061
+ {
2062
+ value: widget.title,
2063
+ onChange: (e) => onUpdate(widget.id, { title: e.target.value }),
2064
+ style: { ...inputStyle2, flex: 1, fontSize: 13, fontWeight: 600, padding: "2px 4px" }
2065
+ }
2066
+ ),
2067
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 2 }, children: WIDGET_TYPES.map((wt) => /* @__PURE__ */ jsxRuntime.jsx(
2068
+ "button",
2069
+ {
2070
+ onClick: () => onUpdate(widget.id, { type: wt.type }),
2071
+ title: wt.label,
2072
+ style: {
2073
+ width: 24,
2074
+ height: 24,
2075
+ display: "flex",
2076
+ alignItems: "center",
2077
+ justifyContent: "center",
2078
+ fontSize: 12,
2079
+ borderRadius: 4,
2080
+ border: widget.type === wt.type ? "1px solid #3b82f6" : "1px solid transparent",
2081
+ backgroundColor: widget.type === wt.type ? "#eff6ff" : "transparent",
2082
+ color: widget.type === wt.type ? "#3b82f6" : "#9ca3af",
2083
+ cursor: "pointer"
2084
+ },
2085
+ children: wt.icon
2086
+ },
2087
+ wt.type
2088
+ )) }),
2089
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onRemove(widget.id), style: { background: "none", border: "none", cursor: "pointer", fontSize: 14, color: "#d1d5db", padding: "0 2px" }, title: "Remove widget", children: "\xD7" })
2090
+ ] }),
2091
+ !collapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "8px 12px" }, children: [
2092
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 8 }, children: [
2093
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 10, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 4 }, children: "Metrics" }),
2094
+ widget.metrics.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#d1d5db", fontStyle: "italic" }, children: "Click a metric in the catalog to add" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 4, flexWrap: "wrap" }, children: widget.metrics.map((m) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...chipStyle, backgroundColor: "#ecfdf5", borderColor: "#a7f3d0", color: "#065f46" }, children: [
2095
+ m.displayName,
2096
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onRemoveField(widget.id, m.name, "metric"), style: chipRemoveStyle, children: "\xD7" })
2097
+ ] }, m.name)) })
2098
+ ] }),
2099
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 8 }, children: [
2100
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 10, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 4 }, children: "Group By" }),
2101
+ widget.groupBy.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#d1d5db", fontStyle: "italic" }, children: "Click a dimension in the catalog to add" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 4, flexWrap: "wrap" }, children: widget.groupBy.map((d) => /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...chipStyle, backgroundColor: "#eef2ff", borderColor: "#c7d2fe", color: "#3730a3" }, children: [
2102
+ d.displayName,
2103
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onRemoveField(widget.id, d.name, "groupBy"), style: chipRemoveStyle, children: "\xD7" })
2104
+ ] }, d.name)) })
2105
+ ] }),
2106
+ hasTimeDim && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
2107
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, fontWeight: 600, color: "#6b7280", textTransform: "uppercase" }, children: "Grain" }),
2108
+ /* @__PURE__ */ jsxRuntime.jsxs(
2109
+ "select",
2110
+ {
2111
+ value: widget.grain ?? "",
2112
+ onChange: (e) => onUpdate(widget.id, { grain: e.target.value || null }),
2113
+ style: { ...selectStyle2, width: "auto", fontSize: 11 },
2114
+ children: [
2115
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "None" }),
2116
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: "Day" }),
2117
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: "Week" }),
2118
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "month", children: "Month" }),
2119
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "quarter", children: "Quarter" }),
2120
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "year", children: "Year" })
2121
+ ]
2122
+ }
2123
+ )
2124
+ ] })
2125
+ ] })
2126
+ ] });
2127
+ }
2128
+ async function copyToClipboard(text) {
2129
+ try {
2130
+ await navigator.clipboard.writeText(text);
2131
+ } catch {
2132
+ const ta = document.createElement("textarea");
2133
+ ta.value = text;
2134
+ document.body.appendChild(ta);
2135
+ ta.select();
2136
+ document.execCommand("copy");
2137
+ document.body.removeChild(ta);
2138
+ }
2139
+ }
1432
2140
  var backdropStyle = {
1433
2141
  position: "fixed",
1434
2142
  inset: 0,
@@ -1441,7 +2149,7 @@ var modalStyle = {
1441
2149
  left: "50%",
1442
2150
  transform: "translate(-50%, -50%)",
1443
2151
  zIndex: 100001,
1444
- width: "min(700px, 90vw)",
2152
+ width: "min(900px, 95vw)",
1445
2153
  maxHeight: "85vh",
1446
2154
  backgroundColor: "#fff",
1447
2155
  borderRadius: 12,
@@ -1454,8 +2162,8 @@ var modalStyle = {
1454
2162
  var modalHeaderStyle = {
1455
2163
  display: "flex",
1456
2164
  justifyContent: "space-between",
1457
- alignItems: "flex-start",
1458
- padding: "16px 20px",
2165
+ alignItems: "center",
2166
+ padding: "0 20px",
1459
2167
  borderBottom: "1px solid #e5e7eb"
1460
2168
  };
1461
2169
  var closeBtnStyle = {
@@ -1471,7 +2179,7 @@ var sectionStyle = {
1471
2179
  padding: "12px 20px"
1472
2180
  };
1473
2181
  var sectionTitleStyle = {
1474
- fontSize: 11,
2182
+ fontSize: 10,
1475
2183
  fontWeight: 700,
1476
2184
  textTransform: "uppercase",
1477
2185
  letterSpacing: "0.05em",
@@ -1515,7 +2223,6 @@ var mappingTdStyle = {
1515
2223
  verticalAlign: "middle"
1516
2224
  };
1517
2225
  var selectStyle2 = {
1518
- width: "100%",
1519
2226
  padding: "4px 6px",
1520
2227
  fontSize: 12,
1521
2228
  border: "1px solid #d1d5db",
@@ -1523,15 +2230,67 @@ var selectStyle2 = {
1523
2230
  backgroundColor: "#fff",
1524
2231
  color: "#111827"
1525
2232
  };
1526
- var generateBtnStyle = {
1527
- width: "100%",
2233
+ var inputStyle2 = {
2234
+ padding: "4px 8px",
2235
+ fontSize: 12,
2236
+ border: "1px solid #d1d5db",
2237
+ borderRadius: 4,
2238
+ backgroundColor: "#fff",
2239
+ color: "#111827",
2240
+ outline: "none",
2241
+ fontFamily: "system-ui, -apple-system, sans-serif"
2242
+ };
2243
+ var codePreviewStyle = {
2244
+ fontSize: 11,
2245
+ fontFamily: "monospace",
2246
+ backgroundColor: "#f9fafb",
2247
+ border: "1px solid #e5e7eb",
2248
+ borderRadius: 6,
2249
+ padding: "8px 12px",
2250
+ whiteSpace: "pre-wrap",
2251
+ color: "#374151",
2252
+ margin: 0
2253
+ };
2254
+ var primaryBtnStyle = {
1528
2255
  padding: "10px 16px",
1529
2256
  fontSize: 14,
1530
2257
  fontWeight: 600,
1531
2258
  color: "#fff",
1532
2259
  backgroundColor: "#3b82f6",
1533
2260
  border: "none",
1534
- borderRadius: 8
2261
+ borderRadius: 8,
2262
+ fontFamily: "system-ui, -apple-system, sans-serif"
2263
+ };
2264
+ var secondaryBtnStyle = {
2265
+ padding: "10px 16px",
2266
+ fontSize: 14,
2267
+ fontWeight: 600,
2268
+ color: "#374151",
2269
+ backgroundColor: "#fff",
2270
+ border: "1px solid #d1d5db",
2271
+ borderRadius: 8,
2272
+ fontFamily: "system-ui, -apple-system, sans-serif"
2273
+ };
2274
+ var chipStyle = {
2275
+ display: "inline-flex",
2276
+ alignItems: "center",
2277
+ gap: 4,
2278
+ padding: "2px 8px",
2279
+ fontSize: 11,
2280
+ fontWeight: 500,
2281
+ borderRadius: 999,
2282
+ border: "1px solid #e5e7eb",
2283
+ backgroundColor: "#f9fafb",
2284
+ color: "#374151"
2285
+ };
2286
+ var chipRemoveStyle = {
2287
+ background: "none",
2288
+ border: "none",
2289
+ cursor: "pointer",
2290
+ fontSize: 12,
2291
+ color: "#9ca3af",
2292
+ padding: 0,
2293
+ lineHeight: 1
1535
2294
  };
1536
2295
 
1537
2296
  exports.AdminDashboard = AdminDashboard;
@@ -1541,8 +2300,11 @@ exports.Inspectable = Inspectable;
1541
2300
  exports.MetricPicker = MetricPicker;
1542
2301
  exports.ResultsTable = ResultsTable;
1543
2302
  exports.analyzeChartData = analyzeChartData;
2303
+ exports.canOpenInLovable = canOpenInLovable;
1544
2304
  exports.generateMigrationPrompt = generateMigrationPrompt;
2305
+ exports.generateReportPrompt = generateReportPrompt;
1545
2306
  exports.matchField = matchField;
1546
2307
  exports.matchFields = matchFields;
2308
+ exports.openInLovable = openInLovable;
1547
2309
  //# sourceMappingURL=components.cjs.map
1548
2310
  //# sourceMappingURL=components.cjs.map