@inkindcards/semantic-layer 2.2.6 → 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
  }
@@ -998,51 +1001,309 @@ function matchDisplayName(column, catalog) {
998
1001
  if (lower.length < 3) return null;
999
1002
  return catalog.find((f) => f.displayName.toLowerCase().includes(lower)) ?? catalog.find((f) => f.description.toLowerCase().includes(lower)) ?? null;
1000
1003
  }
1004
+ function matchField(column, catalog) {
1005
+ const exact = matchExact(column, catalog);
1006
+ if (exact) return { field: exact, confidence: "exact" };
1007
+ const normalized = matchNormalized(column, catalog);
1008
+ if (normalized) return { field: normalized, confidence: "fuzzy" };
1009
+ const display = matchDisplayName(column, catalog);
1010
+ if (display) return { field: display, confidence: "fuzzy" };
1011
+ return { field: null, confidence: "manual" };
1012
+ }
1001
1013
  function matchFields(columns, catalog) {
1002
1014
  return columns.map((column) => {
1003
- const exact = matchExact(column, catalog);
1004
- if (exact) return { column, suggestedField: exact, confidence: "exact" };
1005
- const normalized = matchNormalized(column, catalog);
1006
- if (normalized) return { column, suggestedField: normalized, confidence: "fuzzy" };
1007
- const display = matchDisplayName(column, catalog);
1008
- if (display) return { column, suggestedField: display, confidence: "fuzzy" };
1009
- return { column, suggestedField: null, confidence: "manual" };
1015
+ const { field, confidence } = matchField(column, catalog);
1016
+ return { column, suggestedField: field, confidence };
1010
1017
  });
1011
1018
  }
1019
+ function analyzeChartData(columns, sampleValues) {
1020
+ const dimensionColumns = [];
1021
+ const metricColumns = [];
1022
+ for (const col of columns) {
1023
+ const values = sampleValues.map((row) => row[col]).filter((v) => v !== null && v !== void 0);
1024
+ if (values.length === 0) {
1025
+ dimensionColumns.push(col);
1026
+ continue;
1027
+ }
1028
+ const numericCount = values.filter((v) => typeof v === "number" || typeof v === "string" && !isNaN(Number(v)) && v.trim() !== "").length;
1029
+ const isNumeric = numericCount / values.length > 0.5;
1030
+ if (isNumeric) {
1031
+ metricColumns.push(col);
1032
+ } else {
1033
+ dimensionColumns.push(col);
1034
+ }
1035
+ }
1036
+ return { dimensionColumns, metricColumns };
1037
+ }
1012
1038
 
1013
1039
  // src/components/prompt-generator.ts
1014
- function generateMigrationPrompt(componentLabel, mappings) {
1015
- const resolved = mappings.filter((m) => m.field !== null);
1016
- if (resolved.length === 0) {
1040
+ function generateMigrationPrompt(config) {
1041
+ const { componentLabel, dimensions, metrics, grain, originalMetricColumns } = config;
1042
+ if (dimensions.length === 0 && metrics.length === 0) {
1017
1043
  return `Replace the sample/hardcoded data in the "${componentLabel}" component with live data from the semantic layer using useSemanticQuery. Use the DataCatalog to find the right field names.`;
1018
1044
  }
1019
- const metrics = resolved.filter((m) => m.field.type === "metric").map((m) => m.field.name);
1020
- const timeDims = resolved.filter((m) => m.field.type === "time_dimension").map((m) => m.field.name);
1021
- const dims = resolved.filter((m) => m.field.type === "dimension").map((m) => m.field.name);
1022
- const groupBy = [...timeDims, ...dims];
1023
1045
  const parts = [
1024
- `Replace the sample/hardcoded data in the "${componentLabel}" component with a useSemanticQuery call using:`
1046
+ `Replace the sample/hardcoded data in the "${componentLabel}" component with a useSemanticQuery call using:`,
1047
+ ""
1025
1048
  ];
1026
1049
  if (metrics.length > 0) {
1027
- parts.push(` metrics: [${metrics.map((n) => `'${n}'`).join(", ")}]`);
1050
+ parts.push(` metrics: [${metrics.map((m) => `'${m.name}'`).join(", ")}]`);
1028
1051
  }
1029
- if (groupBy.length > 0) {
1052
+ if (dimensions.length > 0) {
1053
+ const groupBy = dimensions.map((d) => d.field.name);
1030
1054
  parts.push(` groupBy: [${groupBy.map((n) => `'${n}'`).join(", ")}]`);
1031
1055
  }
1032
- if (timeDims.length > 0) {
1033
- parts.push(` grain: 'month'`);
1056
+ if (grain) {
1057
+ parts.push(` grain: '${grain}'`);
1034
1058
  }
1035
1059
  parts.push("");
1036
- parts.push("Map the query result columns to the component's props:");
1037
- for (const m of resolved) {
1038
- parts.push(` "${m.column}" \u2192 data column "${m.field.name}"`);
1060
+ const sampleRow = [];
1061
+ for (const d of dimensions) {
1062
+ sampleRow.push(`${d.field.name}: "..."`);
1063
+ }
1064
+ for (const m of metrics) {
1065
+ sampleRow.push(`${m.name}: 1234`);
1066
+ }
1067
+ parts.push("The query returns rows like:");
1068
+ parts.push(` { ${sampleRow.join(", ")} }`);
1069
+ parts.push("");
1070
+ parts.push("Map to the chart's expected format:");
1071
+ for (const d of dimensions) {
1072
+ parts.push(` X-axis / category: "${d.field.name}" (was "${d.column}" in the hardcoded data)`);
1073
+ }
1074
+ if (metrics.length > 0 && originalMetricColumns.length > 0) {
1075
+ parts.push(` Series / values: each metric (${metrics.map((m) => `"${m.name}"`).join(", ")}) becomes a line/area/bar in the chart`);
1076
+ parts.push(` (replaces the old columns: ${originalMetricColumns.map((c) => `"${c}"`).join(", ")})`);
1039
1077
  }
1040
1078
  parts.push("");
1041
1079
  parts.push(
1042
- "Remove the old hardcoded/sample data and any fetch logic it used. Use the data, isLoading, and error values from useSemanticQuery to render the component."
1080
+ "Remove the old hardcoded/sample data and any fetch logic it used. Use the data, isLoading, and error values from useSemanticQuery to render the component. Show a loading spinner while isLoading is true. Show an error message if error is not null."
1043
1081
  );
1044
1082
  return parts.join("\n");
1045
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
+ }
1046
1307
  function DataInspector({ children }) {
1047
1308
  return /* @__PURE__ */ jsxRuntime.jsxs(InspectableRegistry, { children: [
1048
1309
  /* @__PURE__ */ jsxRuntime.jsx(InspectorOverlay, {}),
@@ -1052,7 +1313,15 @@ function DataInspector({ children }) {
1052
1313
  function InspectorOverlay() {
1053
1314
  const registry = react.useContext(InspectableRegistryContext);
1054
1315
  if (!registry) return null;
1055
- 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;
1056
1325
  react.useEffect(() => {
1057
1326
  function handleKeyDown(e) {
1058
1327
  if (e.ctrlKey && e.shiftKey && e.key === "I") {
@@ -1063,38 +1332,60 @@ function InspectorOverlay() {
1063
1332
  window.addEventListener("keydown", handleKeyDown);
1064
1333
  return () => window.removeEventListener("keydown", handleKeyDown);
1065
1334
  }, [inspectActive, setInspectActive]);
1335
+ react.useEffect(() => {
1336
+ if (selectedId) setModalTab("migrate");
1337
+ }, [selectedId, setModalTab]);
1066
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]);
1067
1356
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1068
1357
  /* @__PURE__ */ jsxRuntime.jsx(
1069
1358
  ToggleButton,
1070
1359
  {
1071
1360
  active: inspectActive,
1361
+ modalOpen: modalTab !== null,
1072
1362
  count: entries.size,
1073
- onToggle: () => {
1074
- setInspectActive(!inspectActive);
1075
- if (inspectActive) setSelectedId(null);
1076
- }
1363
+ onToggle: handleToggle
1077
1364
  }
1078
1365
  ),
1079
- selectedEntry && /* @__PURE__ */ jsxRuntime.jsx(
1080
- InspectorModal,
1366
+ modalTab && /* @__PURE__ */ jsxRuntime.jsx(
1367
+ UnifiedModal,
1081
1368
  {
1369
+ tab: modalTab,
1370
+ onTabChange: handleTabChange,
1082
1371
  entry: selectedEntry,
1083
- onClose: () => setSelectedId(null)
1372
+ onClose: handleClose
1084
1373
  }
1085
1374
  )
1086
1375
  ] });
1087
1376
  }
1088
1377
  function ToggleButton({
1089
1378
  active,
1379
+ modalOpen,
1090
1380
  count,
1091
1381
  onToggle
1092
1382
  }) {
1383
+ const lit = active || modalOpen;
1093
1384
  return /* @__PURE__ */ jsxRuntime.jsxs(
1094
1385
  "button",
1095
1386
  {
1096
1387
  onClick: onToggle,
1097
- 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)",
1098
1389
  style: {
1099
1390
  position: "fixed",
1100
1391
  bottom: 20,
@@ -1107,9 +1398,9 @@ function ToggleButton({
1107
1398
  fontSize: 13,
1108
1399
  fontWeight: 600,
1109
1400
  fontFamily: "system-ui, -apple-system, sans-serif",
1110
- color: active ? "#fff" : "#374151",
1111
- backgroundColor: active ? "#3b82f6" : "#fff",
1112
- border: `1px solid ${active ? "#2563eb" : "#d1d5db"}`,
1401
+ color: lit ? "#fff" : "#374151",
1402
+ backgroundColor: lit ? "#3b82f6" : "#fff",
1403
+ border: `1px solid ${lit ? "#2563eb" : "#d1d5db"}`,
1113
1404
  borderRadius: 999,
1114
1405
  cursor: "pointer",
1115
1406
  boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
@@ -1120,8 +1411,8 @@ function ToggleButton({
1120
1411
  /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
1121
1412
  /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
1122
1413
  ] }),
1123
- active ? "Inspecting" : "Inspect Data",
1124
- /* @__PURE__ */ jsxRuntime.jsx(
1414
+ lit ? "Active" : "Data Tools",
1415
+ count > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1125
1416
  "span",
1126
1417
  {
1127
1418
  style: {
@@ -1133,8 +1424,8 @@ function ToggleButton({
1133
1424
  fontSize: 11,
1134
1425
  fontWeight: 700,
1135
1426
  borderRadius: 999,
1136
- backgroundColor: active ? "rgba(255,255,255,0.25)" : "#e5e7eb",
1137
- color: active ? "#fff" : "#6b7280",
1427
+ backgroundColor: lit ? "rgba(255,255,255,0.25)" : "#e5e7eb",
1428
+ color: lit ? "#fff" : "#6b7280",
1138
1429
  padding: "0 5px"
1139
1430
  },
1140
1431
  children: count
@@ -1144,154 +1435,708 @@ function ToggleButton({
1144
1435
  }
1145
1436
  );
1146
1437
  }
1147
- function InspectorModal({
1438
+ function UnifiedModal({
1439
+ tab,
1440
+ onTabChange,
1148
1441
  entry,
1149
1442
  onClose
1150
1443
  }) {
1151
- const { fields: catalog, error: catalogError, isLoading: catalogLoading } = chunkT2C43AAL_cjs.useMetrics();
1152
- const initialMatches = react.useMemo(
1153
- () => matchFields(entry.columns, catalog),
1154
- [entry.columns, catalog]
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
+ }
1155
1477
  );
1156
- const [mappings, setMappings] = react.useState(
1157
- () => new Map(initialMatches.map((m) => [m.column, m.suggestedField]))
1478
+ }
1479
+ function MigrateTab({
1480
+ entry,
1481
+ onClose: _onClose
1482
+ }) {
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
+ }) {
1498
+ const analysis = react.useMemo(
1499
+ () => analyzeChartData(entry.columns, entry.sampleValues),
1500
+ [entry.columns, entry.sampleValues]
1158
1501
  );
1502
+ const dimensions = catalog.filter((f) => f.type === "dimension" || f.type === "time_dimension");
1503
+ const metrics = catalog.filter((f) => f.type === "metric");
1504
+ const initialDimMappings = react.useMemo(() => {
1505
+ const map = /* @__PURE__ */ new Map();
1506
+ for (const col of analysis.dimensionColumns) {
1507
+ const { field } = matchField(col, dimensions);
1508
+ map.set(col, field);
1509
+ }
1510
+ return map;
1511
+ }, [analysis.dimensionColumns, dimensions]);
1512
+ const [dimMappings, setDimMappings] = react.useState(initialDimMappings);
1159
1513
  react.useEffect(() => {
1160
- setMappings(new Map(initialMatches.map((m) => [m.column, m.suggestedField])));
1161
- }, [initialMatches]);
1514
+ setDimMappings(initialDimMappings);
1515
+ }, [initialDimMappings]);
1516
+ const [selectedMetrics, setSelectedMetrics] = react.useState([]);
1517
+ const hasTimeDim = Array.from(dimMappings.values()).some((f) => f?.type === "time_dimension");
1518
+ const [grain, setGrain] = react.useState("month");
1162
1519
  const [copied, setCopied] = react.useState(false);
1163
- const handleFieldChange = react.useCallback((column, field) => {
1164
- setMappings((prev) => {
1520
+ const handleDimChange = react.useCallback((col, field) => {
1521
+ setDimMappings((prev) => {
1165
1522
  const next = new Map(prev);
1166
- next.set(column, field);
1523
+ next.set(col, field);
1167
1524
  return next;
1168
1525
  });
1169
1526
  }, []);
1527
+ const handleToggleMetric = react.useCallback((field) => {
1528
+ setSelectedMetrics(
1529
+ (prev) => prev.some((m) => m.name === field.name) ? prev.filter((m) => m.name !== field.name) : [...prev, field]
1530
+ );
1531
+ }, []);
1170
1532
  const handleGenerate = react.useCallback(async () => {
1171
- const fieldMappings = entry.columns.map((col) => ({
1172
- column: col,
1173
- field: mappings.get(col) ?? null
1174
- }));
1175
- const prompt = generateMigrationPrompt(entry.label, fieldMappings);
1176
- try {
1177
- await navigator.clipboard.writeText(prompt);
1178
- setCopied(true);
1179
- setTimeout(() => setCopied(false), 2e3);
1180
- } catch {
1181
- const ta = document.createElement("textarea");
1182
- ta.value = prompt;
1183
- document.body.appendChild(ta);
1184
- ta.select();
1185
- document.execCommand("copy");
1186
- document.body.removeChild(ta);
1187
- setCopied(true);
1188
- setTimeout(() => setCopied(false), 2e3);
1533
+ const resolvedDims = [];
1534
+ for (const [col, field] of dimMappings) {
1535
+ if (field) resolvedDims.push({ column: col, field });
1189
1536
  }
1190
- }, [entry, mappings]);
1191
- const confidenceMap = react.useMemo(() => {
1192
- const map = /* @__PURE__ */ new Map();
1193
- for (const m of initialMatches) map.set(m.column, m.confidence);
1194
- return map;
1195
- }, [initialMatches]);
1537
+ const migrationConfig = {
1538
+ componentLabel: entry.label,
1539
+ dimensions: resolvedDims,
1540
+ metrics: selectedMetrics,
1541
+ grain: hasTimeDim && grain ? grain : null,
1542
+ originalMetricColumns: analysis.metricColumns
1543
+ };
1544
+ const prompt = generateMigrationPrompt(migrationConfig);
1545
+ await copyToClipboard(prompt);
1546
+ setCopied(true);
1547
+ setTimeout(() => setCopied(false), 2e3);
1548
+ }, [entry, dimMappings, selectedMetrics, grain, hasTimeDim, analysis.metricColumns]);
1549
+ const canGenerate = selectedMetrics.length > 0 || Array.from(dimMappings.values()).some(Boolean);
1196
1550
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1197
- /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: onClose, style: backdropStyle }),
1198
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyle, children: [
1199
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalHeaderStyle, children: [
1200
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1201
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 700, color: "#111827" }, children: entry.label }),
1202
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 12, color: "#6b7280", marginTop: 2 }, children: [
1203
- "Source: ",
1204
- entry.currentSource
1205
- ] })
1206
- ] }),
1207
- /* @__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
+ ] })
1208
1558
  ] }),
1209
1559
  entry.sampleValues.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1210
1560
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Current Data Preview" }),
1211
1561
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: previewTableStyle, children: [
1212
1562
  /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { style: previewThStyle, children: col }, col)) }) }),
1213
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)) })
1214
- ] }) })
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
+ ] })
1215
1581
  ] }),
1216
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
1217
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Field Mapping" }),
1218
- /* @__PURE__ */ jsxRuntime.jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13 }, children: [
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: [
1219
1587
  /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1220
1588
  /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Current Column" }),
1221
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Confidence" }),
1222
- /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Semantic Layer Field" })
1589
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Sample Values" }),
1590
+ /* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Semantic Layer Dimension" })
1223
1591
  ] }) }),
1224
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: entry.columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
1225
- MappingRow,
1226
- {
1227
- column: col,
1228
- confidence: confidenceMap.get(col) ?? "manual",
1229
- selectedField: mappings.get(col) ?? null,
1230
- catalog,
1231
- onChange: (field) => handleFieldChange(col, field)
1232
- },
1233
- col
1234
- )) })
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
+ }) })
1235
1623
  ] }),
1236
- catalog.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, color: "#9ca3af", marginTop: 8, textAlign: "center" }, children: catalogLoading ? "Loading catalog..." : catalogError ? `Error loading catalog: ${catalogError}` : "No semantic layer catalog available. Connect to the gateway to enable smart matching." })
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(", "),
1642
+ ")"
1643
+ ] })
1644
+ ] }),
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
+ )) })
1237
1797
  ] }),
1238
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "12px 20px", borderTop: "1px solid #e5e7eb" }, children: /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: handleGenerate, style: generateBtnStyle, children: copied ? "Copied to clipboard!" : "Generate Migration Prompt" }) })
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
+ ] })
1828
+ ] })
1829
+ ] }),
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)) })
1854
+ ] }),
1855
+ canGenerate && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 12 }, children: [
1856
+ /* @__PURE__ */ jsxRuntime.jsxs(
1857
+ "button",
1858
+ {
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" },
1861
+ children: [
1862
+ previewOpen ? "\u25BC" : "\u25B6",
1863
+ " Prompt Preview"
1864
+ ]
1865
+ }
1866
+ ),
1867
+ previewOpen && /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { ...codePreviewStyle, marginTop: 8, maxHeight: 200, overflow: "auto", fontSize: 10 }, children: prompt })
1868
+ ] })
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(
1874
+ "button",
1875
+ {
1876
+ onClick: handleCopy,
1877
+ disabled: !canGenerate,
1878
+ style: {
1879
+ ...secondaryBtnStyle,
1880
+ flex: 1,
1881
+ opacity: canGenerate ? 1 : 0.5,
1882
+ cursor: canGenerate ? "pointer" : "not-allowed"
1883
+ },
1884
+ children: copied ? "Copied!" : "Copy Prompt"
1885
+ }
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
+ )
1239
1912
  ] })
1240
1913
  ] });
1241
1914
  }
1242
- function MappingRow({
1243
- column,
1244
- confidence,
1245
- selectedField,
1246
- catalog,
1247
- onChange
1915
+ function CatalogChip({
1916
+ field,
1917
+ widgets,
1918
+ onAddToWidget,
1919
+ onAddAsFilter
1248
1920
  }) {
1249
- const badgeColors = {
1250
- exact: { bg: "#dcfce7", color: "#15803d" },
1251
- fuzzy: { bg: "#fef9c3", color: "#a16207" },
1252
- manual: { bg: "#fee2e2", color: "#dc2626" }
1253
- };
1254
- const badge = badgeColors[confidence] ?? badgeColors.manual;
1255
- return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1256
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsxRuntime.jsx("code", { style: { fontSize: 12, color: "#6366f1", fontFamily: "monospace" }, children: column }) }),
1257
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: { ...mappingTdStyle, textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1258
- "span",
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",
1259
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}`,
1260
1937
  style: {
1261
- display: "inline-block",
1262
- padding: "1px 6px",
1263
- fontSize: 10,
1264
- fontWeight: 600,
1265
- borderRadius: 4,
1266
- backgroundColor: badge.bg,
1267
- color: badge.color
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
1268
1952
  },
1269
- children: confidence
1270
- }
1271
- ) }),
1272
- /* @__PURE__ */ jsxRuntime.jsx("td", { style: mappingTdStyle, children: catalog.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
1273
- "select",
1274
- {
1275
- value: selectedField?.name ?? "",
1276
- onChange: (e) => {
1277
- const field = catalog.find((f) => f.name === e.target.value) ?? null;
1278
- onChange(field);
1953
+ onMouseEnter: (e) => {
1954
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
1955
+ },
1956
+ onMouseLeave: (e) => {
1957
+ e.currentTarget.style.backgroundColor = "transparent";
1279
1958
  },
1280
- style: selectStyle2,
1281
1959
  children: [
1282
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "-- none --" }),
1283
- catalog.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: f.name, children: [
1284
- f.displayName,
1285
- " (",
1286
- f.type,
1287
- ") \u2014 ",
1288
- f.name
1289
- ] }, f.name))
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 })
1290
1962
  ]
1291
1963
  }
1292
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, color: "#9ca3af" }, children: selectedField ? selectedField.name : "No catalog" }) })
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
+ ] })
2004
+ ] })
2005
+ ] });
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
+ ] })
1293
2126
  ] });
1294
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
+ }
1295
2140
  var backdropStyle = {
1296
2141
  position: "fixed",
1297
2142
  inset: 0,
@@ -1304,8 +2149,8 @@ var modalStyle = {
1304
2149
  left: "50%",
1305
2150
  transform: "translate(-50%, -50%)",
1306
2151
  zIndex: 100001,
1307
- width: "min(640px, 90vw)",
1308
- maxHeight: "80vh",
2152
+ width: "min(900px, 95vw)",
2153
+ maxHeight: "85vh",
1309
2154
  backgroundColor: "#fff",
1310
2155
  borderRadius: 12,
1311
2156
  boxShadow: "0 8px 30px rgba(0,0,0,0.2)",
@@ -1317,8 +2162,8 @@ var modalStyle = {
1317
2162
  var modalHeaderStyle = {
1318
2163
  display: "flex",
1319
2164
  justifyContent: "space-between",
1320
- alignItems: "flex-start",
1321
- padding: "16px 20px",
2165
+ alignItems: "center",
2166
+ padding: "0 20px",
1322
2167
  borderBottom: "1px solid #e5e7eb"
1323
2168
  };
1324
2169
  var closeBtnStyle = {
@@ -1331,17 +2176,15 @@ var closeBtnStyle = {
1331
2176
  lineHeight: 1
1332
2177
  };
1333
2178
  var sectionStyle = {
1334
- padding: "12px 20px",
1335
- overflow: "auto",
1336
- flex: 1
2179
+ padding: "12px 20px"
1337
2180
  };
1338
2181
  var sectionTitleStyle = {
1339
- fontSize: 11,
2182
+ fontSize: 10,
1340
2183
  fontWeight: 700,
1341
2184
  textTransform: "uppercase",
1342
2185
  letterSpacing: "0.05em",
1343
2186
  color: "#6b7280",
1344
- marginBottom: 8
2187
+ marginBottom: 4
1345
2188
  };
1346
2189
  var previewTableStyle = {
1347
2190
  width: "100%",
@@ -1380,7 +2223,6 @@ var mappingTdStyle = {
1380
2223
  verticalAlign: "middle"
1381
2224
  };
1382
2225
  var selectStyle2 = {
1383
- width: "100%",
1384
2226
  padding: "4px 6px",
1385
2227
  fontSize: 12,
1386
2228
  border: "1px solid #d1d5db",
@@ -1388,8 +2230,28 @@ var selectStyle2 = {
1388
2230
  backgroundColor: "#fff",
1389
2231
  color: "#111827"
1390
2232
  };
1391
- var generateBtnStyle = {
1392
- 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 = {
1393
2255
  padding: "10px 16px",
1394
2256
  fontSize: 14,
1395
2257
  fontWeight: 600,
@@ -1397,7 +2259,38 @@ var generateBtnStyle = {
1397
2259
  backgroundColor: "#3b82f6",
1398
2260
  border: "none",
1399
2261
  borderRadius: 8,
1400
- cursor: "pointer"
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
1401
2294
  };
1402
2295
 
1403
2296
  exports.AdminDashboard = AdminDashboard;
@@ -1406,7 +2299,12 @@ exports.DataInspector = DataInspector;
1406
2299
  exports.Inspectable = Inspectable;
1407
2300
  exports.MetricPicker = MetricPicker;
1408
2301
  exports.ResultsTable = ResultsTable;
2302
+ exports.analyzeChartData = analyzeChartData;
2303
+ exports.canOpenInLovable = canOpenInLovable;
1409
2304
  exports.generateMigrationPrompt = generateMigrationPrompt;
2305
+ exports.generateReportPrompt = generateReportPrompt;
2306
+ exports.matchField = matchField;
1410
2307
  exports.matchFields = matchFields;
2308
+ exports.openInLovable = openInLovable;
1411
2309
  //# sourceMappingURL=components.cjs.map
1412
2310
  //# sourceMappingURL=components.cjs.map