@inkindcards/semantic-layer 2.3.0 → 2.4.1
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.
- package/dist/components.cjs +1136 -202
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.cts +30 -2
- package/dist/components.d.ts +30 -2
- package/dist/components.js +1133 -203
- package/dist/components.js.map +1 -1
- package/package.json +1 -1
package/dist/components.cjs
CHANGED
|
@@ -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,255 @@ function generateMigrationPrompt(config) {
|
|
|
1078
1081
|
);
|
|
1079
1082
|
return parts.join("\n");
|
|
1080
1083
|
}
|
|
1084
|
+
|
|
1085
|
+
// src/components/report-prompt-generator.ts
|
|
1086
|
+
var TIME_PRESET_LABELS = {
|
|
1087
|
+
"12m": "12 months",
|
|
1088
|
+
"6m": "6 months",
|
|
1089
|
+
"3m": "3 months",
|
|
1090
|
+
"1m": "1 month",
|
|
1091
|
+
"1w": "1 week",
|
|
1092
|
+
"1d": "1 day"
|
|
1093
|
+
};
|
|
1094
|
+
var PALETTE = [
|
|
1095
|
+
"#3b82f6",
|
|
1096
|
+
"#10b981",
|
|
1097
|
+
"#f59e0b",
|
|
1098
|
+
"#ef4444",
|
|
1099
|
+
"#8b5cf6",
|
|
1100
|
+
"#ec4899",
|
|
1101
|
+
"#06b6d4",
|
|
1102
|
+
"#84cc16",
|
|
1103
|
+
"#f97316",
|
|
1104
|
+
"#6366f1"
|
|
1105
|
+
];
|
|
1106
|
+
function generateReportPrompt(config) {
|
|
1107
|
+
const { dashboardTitle, layout, widgets, sharedFilters } = config;
|
|
1108
|
+
if (widgets.length === 0) {
|
|
1109
|
+
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.`;
|
|
1110
|
+
}
|
|
1111
|
+
const parts = [];
|
|
1112
|
+
parts.push(`Create a dashboard page called "${dashboardTitle}" using the @inkindcards/semantic-layer SDK to query live data from the dbt Semantic Layer.`);
|
|
1113
|
+
parts.push("");
|
|
1114
|
+
parts.push("## Setup");
|
|
1115
|
+
parts.push("");
|
|
1116
|
+
parts.push("Install dependencies if not already present:");
|
|
1117
|
+
parts.push(" npm install @inkindcards/semantic-layer recharts");
|
|
1118
|
+
parts.push("");
|
|
1119
|
+
parts.push("Imports needed:");
|
|
1120
|
+
parts.push("```tsx");
|
|
1121
|
+
parts.push(`import { useSemanticQuery } from "@inkindcards/semantic-layer/react";`);
|
|
1122
|
+
const chartTypes = new Set(widgets.map((w) => w.type));
|
|
1123
|
+
const rechartsImports = ["ResponsiveContainer", "Tooltip", "Legend"];
|
|
1124
|
+
if (chartTypes.has("line")) rechartsImports.push("LineChart", "Line", "XAxis", "YAxis", "CartesianGrid");
|
|
1125
|
+
if (chartTypes.has("bar")) rechartsImports.push("BarChart", "Bar", "XAxis", "YAxis", "CartesianGrid");
|
|
1126
|
+
if (chartTypes.has("area")) rechartsImports.push("AreaChart", "Area", "XAxis", "YAxis", "CartesianGrid");
|
|
1127
|
+
if (chartTypes.has("pie")) rechartsImports.push("PieChart", "Pie", "Cell");
|
|
1128
|
+
const unique = [...new Set(rechartsImports)];
|
|
1129
|
+
if (unique.length > 3) {
|
|
1130
|
+
parts.push(`import { ${unique.join(", ")} } from "recharts";`);
|
|
1131
|
+
}
|
|
1132
|
+
parts.push("```");
|
|
1133
|
+
parts.push("");
|
|
1134
|
+
if (sharedFilters.length > 0) {
|
|
1135
|
+
parts.push("## Shared Filters");
|
|
1136
|
+
parts.push("");
|
|
1137
|
+
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.");
|
|
1138
|
+
parts.push("");
|
|
1139
|
+
parts.push("```tsx");
|
|
1140
|
+
for (const f of sharedFilters) {
|
|
1141
|
+
parts.push(`const [filter_${sanitize(f.dimension.name)}, setFilter_${sanitize(f.dimension.name)}] = useState<string[]>([]);`);
|
|
1142
|
+
}
|
|
1143
|
+
parts.push("");
|
|
1144
|
+
parts.push("const filters: Record<string, string[]> = {");
|
|
1145
|
+
for (const f of sharedFilters) {
|
|
1146
|
+
parts.push(` "${f.dimension.name}": filter_${sanitize(f.dimension.name)},`);
|
|
1147
|
+
}
|
|
1148
|
+
parts.push("};");
|
|
1149
|
+
parts.push("```");
|
|
1150
|
+
parts.push("");
|
|
1151
|
+
const timeFilters = sharedFilters.filter((f) => f.dimension.type === "time_dimension" && f.timePreset);
|
|
1152
|
+
const dimFilters = sharedFilters.filter((f) => f.dimension.type !== "time_dimension" || !f.timePreset);
|
|
1153
|
+
if (timeFilters.length > 0) {
|
|
1154
|
+
parts.push("For time filters, compute a date range relative to today:");
|
|
1155
|
+
for (const f of timeFilters) {
|
|
1156
|
+
parts.push(` - "${f.dimension.name}": default to the last ${TIME_PRESET_LABELS[f.timePreset]}. Compute a start date (e.g. \`new Date(); startDate.setMonth(startDate.getMonth() - N)\`) and pass it as a filter value. Include preset buttons (${Object.values(TIME_PRESET_LABELS).join(", ")}) so the user can switch ranges.`);
|
|
1157
|
+
}
|
|
1158
|
+
parts.push("");
|
|
1159
|
+
}
|
|
1160
|
+
if (dimFilters.length > 0) {
|
|
1161
|
+
parts.push("For each dimension 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.");
|
|
1162
|
+
parts.push("");
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
parts.push("## Layout");
|
|
1166
|
+
parts.push("");
|
|
1167
|
+
parts.push(layoutInstructions(layout, widgets));
|
|
1168
|
+
parts.push("");
|
|
1169
|
+
parts.push("## Color Palette");
|
|
1170
|
+
parts.push("");
|
|
1171
|
+
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.`);
|
|
1172
|
+
parts.push("");
|
|
1173
|
+
parts.push("## Widgets");
|
|
1174
|
+
parts.push("");
|
|
1175
|
+
for (let i = 0; i < widgets.length; i++) {
|
|
1176
|
+
const w = widgets[i];
|
|
1177
|
+
parts.push(`### ${i + 1}. ${w.title} (${widgetLabel(w.type)})`);
|
|
1178
|
+
parts.push("");
|
|
1179
|
+
parts.push(widgetPrompt(w, sharedFilters.length > 0));
|
|
1180
|
+
parts.push("");
|
|
1181
|
+
}
|
|
1182
|
+
parts.push("## Styling");
|
|
1183
|
+
parts.push("");
|
|
1184
|
+
parts.push("- Use a clean, modern design with rounded corners (border-radius: 12px) and subtle shadows on each card.");
|
|
1185
|
+
parts.push("- Background: white cards on a light gray page (#f9fafb).");
|
|
1186
|
+
parts.push("- Use system-ui / -apple-system font stack, 14px base size.");
|
|
1187
|
+
parts.push("- Each widget should be wrapped in a card with a title header, padding 20px, and a subtle 1px border (#e5e7eb).");
|
|
1188
|
+
parts.push("- Show a shimmer/skeleton loading state while isLoading is true.");
|
|
1189
|
+
parts.push("- Show a styled error banner if error is not null.");
|
|
1190
|
+
parts.push("- Format numbers with Intl.NumberFormat: use commas for large numbers, currency format for revenue/dollar metrics, percentage format for rate metrics.");
|
|
1191
|
+
parts.push("- Charts should fill their container width using ResponsiveContainer with height 300px.");
|
|
1192
|
+
parts.push("- On mobile (< 768px), stack everything in a single column.");
|
|
1193
|
+
parts.push("");
|
|
1194
|
+
return parts.join("\n");
|
|
1195
|
+
}
|
|
1196
|
+
function widgetPrompt(w, hasFilters, index) {
|
|
1197
|
+
const lines = [];
|
|
1198
|
+
const metricsArr = w.metrics.map((m) => `'${m.name}'`).join(", ");
|
|
1199
|
+
const groupByArr = w.groupBy.map((d) => `'${d.name}'`).join(", ");
|
|
1200
|
+
lines.push("Query:");
|
|
1201
|
+
lines.push("```tsx");
|
|
1202
|
+
lines.push(`const { data, isLoading, error } = useSemanticQuery({`);
|
|
1203
|
+
if (w.metrics.length > 0) lines.push(` metrics: [${metricsArr}],`);
|
|
1204
|
+
if (w.groupBy.length > 0) lines.push(` groupBy: [${groupByArr}],`);
|
|
1205
|
+
if (w.grain) lines.push(` grain: '${w.grain}',`);
|
|
1206
|
+
if (hasFilters) lines.push(` filters,`);
|
|
1207
|
+
lines.push(`});`);
|
|
1208
|
+
lines.push("```");
|
|
1209
|
+
lines.push("");
|
|
1210
|
+
switch (w.type) {
|
|
1211
|
+
case "line":
|
|
1212
|
+
case "area": {
|
|
1213
|
+
const ChartTag = w.type === "line" ? "LineChart" : "AreaChart";
|
|
1214
|
+
const SeriesTag = w.type === "line" ? "Line" : "Area";
|
|
1215
|
+
const xField = w.groupBy[0];
|
|
1216
|
+
lines.push(`Render a ${ChartTag} inside a ResponsiveContainer (height 300).`);
|
|
1217
|
+
if (xField) lines.push(`X-axis: "${xField.name}" (label: "${xField.displayName}").`);
|
|
1218
|
+
lines.push(`Add a ${SeriesTag} for each metric:`);
|
|
1219
|
+
for (let i = 0; i < w.metrics.length; i++) {
|
|
1220
|
+
const m = w.metrics[i];
|
|
1221
|
+
lines.push(` - dataKey="${m.name}" name="${m.displayName}" stroke="${PALETTE[i % PALETTE.length]}"${w.type === "area" ? ` fill="${PALETTE[i % PALETTE.length]}" fillOpacity={0.15}` : ""}`);
|
|
1222
|
+
}
|
|
1223
|
+
lines.push("Include CartesianGrid (strokeDasharray='3 3'), Tooltip, and Legend.");
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
case "bar": {
|
|
1227
|
+
const xField = w.groupBy[0];
|
|
1228
|
+
lines.push("Render a BarChart inside a ResponsiveContainer (height 300).");
|
|
1229
|
+
if (xField) lines.push(`X-axis: "${xField.name}" (label: "${xField.displayName}").`);
|
|
1230
|
+
lines.push("Add a Bar for each metric:");
|
|
1231
|
+
for (let i = 0; i < w.metrics.length; i++) {
|
|
1232
|
+
const m = w.metrics[i];
|
|
1233
|
+
lines.push(` - dataKey="${m.name}" name="${m.displayName}" fill="${PALETTE[i % PALETTE.length]}" radius={[4,4,0,0]}`);
|
|
1234
|
+
}
|
|
1235
|
+
lines.push("Include CartesianGrid (strokeDasharray='3 3'), Tooltip, and Legend.");
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
case "pie": {
|
|
1239
|
+
const m = w.metrics[0];
|
|
1240
|
+
const dim = w.groupBy[0];
|
|
1241
|
+
lines.push("Render a PieChart inside a ResponsiveContainer (height 300).");
|
|
1242
|
+
if (m && dim) {
|
|
1243
|
+
lines.push(`Pie dataKey="${m.name}" nameKey="${dim.name}". Label each slice with the dimension value and percentage.`);
|
|
1244
|
+
}
|
|
1245
|
+
lines.push(`Use Cell components to apply these fill colors in order: ${PALETTE.slice(0, 6).map((c) => `"${c}"`).join(", ")}.`);
|
|
1246
|
+
lines.push("Include Tooltip and Legend.");
|
|
1247
|
+
break;
|
|
1248
|
+
}
|
|
1249
|
+
case "table": {
|
|
1250
|
+
lines.push("Render a styled data table with:");
|
|
1251
|
+
lines.push(" - Sticky header row with a light gray background.");
|
|
1252
|
+
lines.push(" - Sortable columns (click header to toggle asc/desc).");
|
|
1253
|
+
lines.push(" - Zebra-striped rows for readability.");
|
|
1254
|
+
const allFields = [...w.groupBy, ...w.metrics];
|
|
1255
|
+
if (allFields.length > 0) {
|
|
1256
|
+
lines.push("Columns:");
|
|
1257
|
+
for (const f of allFields) {
|
|
1258
|
+
const align = f.type === "metric" ? "right-aligned, number-formatted" : "left-aligned";
|
|
1259
|
+
lines.push(` - "${f.displayName}" (key: "${f.name}") \u2014 ${align}`);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
1264
|
+
case "kpi": {
|
|
1265
|
+
const m = w.metrics[0];
|
|
1266
|
+
lines.push("Render a KPI card showing:");
|
|
1267
|
+
if (m) {
|
|
1268
|
+
lines.push(` - Large formatted number for "${m.displayName}" (key: "${m.name}").`);
|
|
1269
|
+
lines.push(` - Use Intl.NumberFormat with appropriate style (currency for revenue/dollar fields, decimal otherwise).`);
|
|
1270
|
+
lines.push(` - Subtitle label: "${m.displayName}".`);
|
|
1271
|
+
}
|
|
1272
|
+
if (w.comparisonMetric) {
|
|
1273
|
+
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.`);
|
|
1274
|
+
}
|
|
1275
|
+
if (w.grain && w.groupBy.length > 0) {
|
|
1276
|
+
lines.push(` - Query with grain '${w.grain}' and take the most recent period's value. Optionally show a sparkline of the trend.`);
|
|
1277
|
+
} else {
|
|
1278
|
+
lines.push(" - Query without groupBy to get the aggregate total.");
|
|
1279
|
+
}
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return lines.join("\n");
|
|
1284
|
+
}
|
|
1285
|
+
function layoutInstructions(layout, widgets) {
|
|
1286
|
+
const kpiCount = widgets.filter((w) => w.type === "kpi").length;
|
|
1287
|
+
const chartCount = widgets.filter((w) => w.type !== "kpi" && w.type !== "table").length;
|
|
1288
|
+
const tableCount = widgets.filter((w) => w.type === "table").length;
|
|
1289
|
+
switch (layout) {
|
|
1290
|
+
case "single":
|
|
1291
|
+
return "Stack all widgets in a single column, full width. Use a flex column with gap 24px.";
|
|
1292
|
+
case "two-column":
|
|
1293
|
+
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`).";
|
|
1294
|
+
case "dashboard":
|
|
1295
|
+
return [
|
|
1296
|
+
"Use a dashboard layout with three zones:",
|
|
1297
|
+
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).",
|
|
1298
|
+
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).",
|
|
1299
|
+
tableCount > 0 ? ` 3. **Table zone** at the bottom: full-width data table(s), stacked.` : " 3. **Table zone**: (none configured)."
|
|
1300
|
+
].join("\n");
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function widgetLabel(type) {
|
|
1304
|
+
const labels = {
|
|
1305
|
+
line: "Line Chart",
|
|
1306
|
+
bar: "Bar Chart",
|
|
1307
|
+
area: "Area Chart",
|
|
1308
|
+
pie: "Pie / Donut Chart",
|
|
1309
|
+
table: "Data Table",
|
|
1310
|
+
kpi: "KPI Card"
|
|
1311
|
+
};
|
|
1312
|
+
return labels[type];
|
|
1313
|
+
}
|
|
1314
|
+
function sanitize(name) {
|
|
1315
|
+
return name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
1316
|
+
}
|
|
1317
|
+
var LOVABLE_CHAR_LIMIT = 5e4;
|
|
1318
|
+
function isRunningInIframe() {
|
|
1319
|
+
try {
|
|
1320
|
+
return window.self !== window.top;
|
|
1321
|
+
} catch {
|
|
1322
|
+
return true;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
function canOpenInLovable(prompt) {
|
|
1326
|
+
return prompt.length <= LOVABLE_CHAR_LIMIT;
|
|
1327
|
+
}
|
|
1328
|
+
function openInLovable(prompt) {
|
|
1329
|
+
const encoded = encodeURIComponent(prompt);
|
|
1330
|
+
const url = `https://lovable.dev/?autosubmit=true#prompt=${encoded}`;
|
|
1331
|
+
window.open(url, "_blank", "noopener");
|
|
1332
|
+
}
|
|
1081
1333
|
function DataInspector({ children }) {
|
|
1082
1334
|
return /* @__PURE__ */ jsxRuntime.jsxs(InspectableRegistry, { children: [
|
|
1083
1335
|
/* @__PURE__ */ jsxRuntime.jsx(InspectorOverlay, {}),
|
|
@@ -1087,7 +1339,15 @@ function DataInspector({ children }) {
|
|
|
1087
1339
|
function InspectorOverlay() {
|
|
1088
1340
|
const registry = react.useContext(InspectableRegistryContext);
|
|
1089
1341
|
if (!registry) return null;
|
|
1090
|
-
const {
|
|
1342
|
+
const {
|
|
1343
|
+
inspectActive,
|
|
1344
|
+
setInspectActive,
|
|
1345
|
+
selectedId,
|
|
1346
|
+
setSelectedId,
|
|
1347
|
+
entries,
|
|
1348
|
+
modalTab,
|
|
1349
|
+
setModalTab
|
|
1350
|
+
} = registry;
|
|
1091
1351
|
react.useEffect(() => {
|
|
1092
1352
|
function handleKeyDown(e) {
|
|
1093
1353
|
if (e.ctrlKey && e.shiftKey && e.key === "I") {
|
|
@@ -1098,38 +1358,60 @@ function InspectorOverlay() {
|
|
|
1098
1358
|
window.addEventListener("keydown", handleKeyDown);
|
|
1099
1359
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1100
1360
|
}, [inspectActive, setInspectActive]);
|
|
1361
|
+
react.useEffect(() => {
|
|
1362
|
+
if (selectedId) setModalTab("migrate");
|
|
1363
|
+
}, [selectedId, setModalTab]);
|
|
1101
1364
|
const selectedEntry = selectedId ? entries.get(selectedId) ?? null : null;
|
|
1365
|
+
const handleClose = react.useCallback(() => {
|
|
1366
|
+
setModalTab(null);
|
|
1367
|
+
setSelectedId(null);
|
|
1368
|
+
}, [setModalTab, setSelectedId]);
|
|
1369
|
+
const handleToggle = react.useCallback(() => {
|
|
1370
|
+
if (modalTab) {
|
|
1371
|
+
handleClose();
|
|
1372
|
+
setInspectActive(false);
|
|
1373
|
+
} else {
|
|
1374
|
+
setModalTab("build");
|
|
1375
|
+
setInspectActive(true);
|
|
1376
|
+
}
|
|
1377
|
+
}, [modalTab, handleClose, setModalTab, setInspectActive]);
|
|
1378
|
+
const handleTabChange = react.useCallback((tab) => {
|
|
1379
|
+
setModalTab(tab);
|
|
1380
|
+
if (tab === "build") setSelectedId(null);
|
|
1381
|
+
}, [setModalTab, setSelectedId]);
|
|
1102
1382
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1103
1383
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1104
1384
|
ToggleButton,
|
|
1105
1385
|
{
|
|
1106
1386
|
active: inspectActive,
|
|
1387
|
+
modalOpen: modalTab !== null,
|
|
1107
1388
|
count: entries.size,
|
|
1108
|
-
onToggle:
|
|
1109
|
-
setInspectActive(!inspectActive);
|
|
1110
|
-
if (inspectActive) setSelectedId(null);
|
|
1111
|
-
}
|
|
1389
|
+
onToggle: handleToggle
|
|
1112
1390
|
}
|
|
1113
1391
|
),
|
|
1114
|
-
|
|
1115
|
-
|
|
1392
|
+
modalTab && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1393
|
+
UnifiedModal,
|
|
1116
1394
|
{
|
|
1395
|
+
tab: modalTab,
|
|
1396
|
+
onTabChange: handleTabChange,
|
|
1117
1397
|
entry: selectedEntry,
|
|
1118
|
-
onClose:
|
|
1398
|
+
onClose: handleClose
|
|
1119
1399
|
}
|
|
1120
1400
|
)
|
|
1121
1401
|
] });
|
|
1122
1402
|
}
|
|
1123
1403
|
function ToggleButton({
|
|
1124
1404
|
active,
|
|
1405
|
+
modalOpen,
|
|
1125
1406
|
count,
|
|
1126
1407
|
onToggle
|
|
1127
1408
|
}) {
|
|
1409
|
+
const lit = active || modalOpen;
|
|
1128
1410
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1129
1411
|
"button",
|
|
1130
1412
|
{
|
|
1131
1413
|
onClick: onToggle,
|
|
1132
|
-
title:
|
|
1414
|
+
title: lit ? "Close data tools (Ctrl+Shift+I)" : "Open data tools (Ctrl+Shift+I)",
|
|
1133
1415
|
style: {
|
|
1134
1416
|
position: "fixed",
|
|
1135
1417
|
bottom: 20,
|
|
@@ -1142,9 +1424,9 @@ function ToggleButton({
|
|
|
1142
1424
|
fontSize: 13,
|
|
1143
1425
|
fontWeight: 600,
|
|
1144
1426
|
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
1145
|
-
color:
|
|
1146
|
-
backgroundColor:
|
|
1147
|
-
border: `1px solid ${
|
|
1427
|
+
color: lit ? "#fff" : "#374151",
|
|
1428
|
+
backgroundColor: lit ? "#3b82f6" : "#fff",
|
|
1429
|
+
border: `1px solid ${lit ? "#2563eb" : "#d1d5db"}`,
|
|
1148
1430
|
borderRadius: 999,
|
|
1149
1431
|
cursor: "pointer",
|
|
1150
1432
|
boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
|
|
@@ -1155,8 +1437,8 @@ function ToggleButton({
|
|
|
1155
1437
|
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
1156
1438
|
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
|
|
1157
1439
|
] }),
|
|
1158
|
-
|
|
1159
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1440
|
+
lit ? "Active" : "Data Tools",
|
|
1441
|
+
count > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1160
1442
|
"span",
|
|
1161
1443
|
{
|
|
1162
1444
|
style: {
|
|
@@ -1168,8 +1450,8 @@ function ToggleButton({
|
|
|
1168
1450
|
fontSize: 11,
|
|
1169
1451
|
fontWeight: 700,
|
|
1170
1452
|
borderRadius: 999,
|
|
1171
|
-
backgroundColor:
|
|
1172
|
-
color:
|
|
1453
|
+
backgroundColor: lit ? "rgba(255,255,255,0.25)" : "#e5e7eb",
|
|
1454
|
+
color: lit ? "#fff" : "#6b7280",
|
|
1173
1455
|
padding: "0 5px"
|
|
1174
1456
|
},
|
|
1175
1457
|
children: count
|
|
@@ -1179,11 +1461,98 @@ function ToggleButton({
|
|
|
1179
1461
|
}
|
|
1180
1462
|
);
|
|
1181
1463
|
}
|
|
1182
|
-
function
|
|
1464
|
+
function UnifiedModal({
|
|
1465
|
+
tab,
|
|
1466
|
+
onTabChange,
|
|
1183
1467
|
entry,
|
|
1184
1468
|
onClose
|
|
1469
|
+
}) {
|
|
1470
|
+
const [dragPos, setDragPos] = react.useState(null);
|
|
1471
|
+
const dragRef = react.useRef(null);
|
|
1472
|
+
const modalRef = react.useRef(null);
|
|
1473
|
+
const handleHeaderMouseDown = react.useCallback((e) => {
|
|
1474
|
+
if (e.target.closest("button")) return;
|
|
1475
|
+
e.preventDefault();
|
|
1476
|
+
const modal = modalRef.current;
|
|
1477
|
+
if (!modal) return;
|
|
1478
|
+
const rect = modal.getBoundingClientRect();
|
|
1479
|
+
dragRef.current = { startX: e.clientX, startY: e.clientY, origX: rect.left, origY: rect.top };
|
|
1480
|
+
const onMove = (ev) => {
|
|
1481
|
+
if (!dragRef.current) return;
|
|
1482
|
+
const dx = ev.clientX - dragRef.current.startX;
|
|
1483
|
+
const dy = ev.clientY - dragRef.current.startY;
|
|
1484
|
+
setDragPos({ x: dragRef.current.origX + dx, y: dragRef.current.origY + dy });
|
|
1485
|
+
};
|
|
1486
|
+
const onUp = () => {
|
|
1487
|
+
dragRef.current = null;
|
|
1488
|
+
document.removeEventListener("mousemove", onMove);
|
|
1489
|
+
document.removeEventListener("mouseup", onUp);
|
|
1490
|
+
};
|
|
1491
|
+
document.addEventListener("mousemove", onMove);
|
|
1492
|
+
document.addEventListener("mouseup", onUp);
|
|
1493
|
+
}, []);
|
|
1494
|
+
const positionStyle = dragPos ? { position: "fixed", top: dragPos.y, left: dragPos.x, transform: "none" } : { position: "fixed", top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
|
|
1495
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1496
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { onClick: onClose, style: backdropStyle }),
|
|
1497
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ref: modalRef, style: { ...modalStyle, ...positionStyle }, children: [
|
|
1498
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1499
|
+
"div",
|
|
1500
|
+
{
|
|
1501
|
+
onMouseDown: handleHeaderMouseDown,
|
|
1502
|
+
style: { ...modalHeaderStyle, cursor: dragRef.current ? "grabbing" : "grab" },
|
|
1503
|
+
children: [
|
|
1504
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 0 }, children: [
|
|
1505
|
+
/* @__PURE__ */ jsxRuntime.jsx(TabButton, { label: "Migrate", active: tab === "migrate", onClick: () => onTabChange("migrate") }),
|
|
1506
|
+
/* @__PURE__ */ jsxRuntime.jsx(TabButton, { label: "Build", active: tab === "build", onClick: () => onTabChange("build") })
|
|
1507
|
+
] }),
|
|
1508
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, style: closeBtnStyle, children: "\xD7" })
|
|
1509
|
+
]
|
|
1510
|
+
}
|
|
1511
|
+
),
|
|
1512
|
+
tab === "migrate" ? /* @__PURE__ */ jsxRuntime.jsx(MigrateTab, { entry, onClose }) : /* @__PURE__ */ jsxRuntime.jsx(BuildTab, {})
|
|
1513
|
+
] })
|
|
1514
|
+
] });
|
|
1515
|
+
}
|
|
1516
|
+
function TabButton({ label, active, onClick }) {
|
|
1517
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1518
|
+
"button",
|
|
1519
|
+
{
|
|
1520
|
+
onClick,
|
|
1521
|
+
style: {
|
|
1522
|
+
padding: "8px 20px",
|
|
1523
|
+
fontSize: 13,
|
|
1524
|
+
fontWeight: 600,
|
|
1525
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
1526
|
+
color: active ? "#3b82f6" : "#6b7280",
|
|
1527
|
+
backgroundColor: "transparent",
|
|
1528
|
+
border: "none",
|
|
1529
|
+
borderBottom: active ? "2px solid #3b82f6" : "2px solid transparent",
|
|
1530
|
+
cursor: "pointer",
|
|
1531
|
+
transition: "all 0.15s ease"
|
|
1532
|
+
},
|
|
1533
|
+
children: label
|
|
1534
|
+
}
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
function MigrateTab({
|
|
1538
|
+
entry,
|
|
1539
|
+
onClose: _onClose
|
|
1185
1540
|
}) {
|
|
1186
1541
|
const { fields: catalog, error: catalogError, isLoading: catalogLoading } = chunkT2C43AAL_cjs.useMetrics();
|
|
1542
|
+
if (!entry) {
|
|
1543
|
+
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: [
|
|
1544
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 14, fontWeight: 500, marginBottom: 8 }, children: "No component selected" }),
|
|
1545
|
+
/* @__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." })
|
|
1546
|
+
] }) });
|
|
1547
|
+
}
|
|
1548
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MigrateTabContent, { entry, catalog, catalogError, catalogLoading });
|
|
1549
|
+
}
|
|
1550
|
+
function MigrateTabContent({
|
|
1551
|
+
entry,
|
|
1552
|
+
catalog,
|
|
1553
|
+
catalogError,
|
|
1554
|
+
catalogLoading
|
|
1555
|
+
}) {
|
|
1187
1556
|
const analysis = react.useMemo(
|
|
1188
1557
|
() => analyzeChartData(entry.columns, entry.sampleValues),
|
|
1189
1558
|
[entry.columns, entry.sampleValues]
|
|
@@ -1231,204 +1600,717 @@ function InspectorModal({
|
|
|
1231
1600
|
originalMetricColumns: analysis.metricColumns
|
|
1232
1601
|
};
|
|
1233
1602
|
const prompt = generateMigrationPrompt(migrationConfig);
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
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
|
-
}
|
|
1603
|
+
await copyToClipboard(prompt);
|
|
1604
|
+
setCopied(true);
|
|
1605
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1248
1606
|
}, [entry, dimMappings, selectedMetrics, grain, hasTimeDim, analysis.metricColumns]);
|
|
1249
1607
|
const canGenerate = selectedMetrics.length > 0 || Array.from(dimMappings.values()).some(Boolean);
|
|
1250
1608
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1251
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
entry.currentSource
|
|
1259
|
-
] })
|
|
1260
|
-
] }),
|
|
1261
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, style: closeBtnStyle, children: "\xD7" })
|
|
1609
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { overflow: "auto", flex: 1 }, children: [
|
|
1610
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
|
|
1611
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 15, fontWeight: 700, color: "#111827" }, children: entry.label }),
|
|
1612
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 12, color: "#6b7280", marginTop: 2 }, children: [
|
|
1613
|
+
"Source: ",
|
|
1614
|
+
entry.currentSource
|
|
1615
|
+
] })
|
|
1262
1616
|
] }),
|
|
1263
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style:
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
1267
|
-
|
|
1268
|
-
|
|
1617
|
+
entry.sampleValues.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
|
|
1618
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Current Data Preview" }),
|
|
1619
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("table", { style: previewTableStyle, children: [
|
|
1620
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { style: previewThStyle, children: col }, col)) }) }),
|
|
1621
|
+
/* @__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)) })
|
|
1622
|
+
] }) }),
|
|
1623
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "#9ca3af" }, children: [
|
|
1624
|
+
"Detected: ",
|
|
1625
|
+
analysis.dimensionColumns.length,
|
|
1626
|
+
" dimension column",
|
|
1627
|
+
analysis.dimensionColumns.length !== 1 ? "s" : "",
|
|
1628
|
+
" (",
|
|
1629
|
+
analysis.dimensionColumns.join(", ") || "none",
|
|
1630
|
+
")",
|
|
1631
|
+
" \xB7 ",
|
|
1632
|
+
analysis.metricColumns.length,
|
|
1633
|
+
" value column",
|
|
1634
|
+
analysis.metricColumns.length !== 1 ? "s" : "",
|
|
1635
|
+
" (",
|
|
1636
|
+
analysis.metricColumns.join(", ") || "none",
|
|
1637
|
+
")"
|
|
1638
|
+
] })
|
|
1639
|
+
] }),
|
|
1640
|
+
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." }) }),
|
|
1641
|
+
catalog.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
|
|
1642
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Dimensions (Group By)" }),
|
|
1643
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: "What categories define the rows?" }),
|
|
1644
|
+
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: [
|
|
1645
|
+
/* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
1646
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Current Column" }),
|
|
1647
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Sample Values" }),
|
|
1648
|
+
/* @__PURE__ */ jsxRuntime.jsx("th", { style: mappingThStyle, children: "Semantic Layer Dimension" })
|
|
1269
1649
|
] }) }),
|
|
1270
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1650
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: analysis.dimensionColumns.map((col) => {
|
|
1651
|
+
const sampleVals = entry.sampleValues.map((row) => row[col]).filter((v) => v !== null && v !== void 0).slice(0, 4).map(String);
|
|
1652
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
|
|
1653
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsxRuntime.jsx("code", { style: { fontSize: 12, color: "#6366f1", fontFamily: "monospace" }, children: col }) }),
|
|
1654
|
+
/* @__PURE__ */ jsxRuntime.jsxs("td", { style: { ...mappingTdStyle, fontSize: 11, color: "#9ca3af" }, children: [
|
|
1655
|
+
sampleVals.join(", "),
|
|
1656
|
+
sampleVals.length >= 4 ? ", ..." : ""
|
|
1657
|
+
] }),
|
|
1658
|
+
/* @__PURE__ */ jsxRuntime.jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1659
|
+
"select",
|
|
1660
|
+
{
|
|
1661
|
+
value: dimMappings.get(col)?.name ?? "",
|
|
1662
|
+
onChange: (e) => {
|
|
1663
|
+
const f = dimensions.find((d) => d.name === e.target.value) ?? null;
|
|
1664
|
+
handleDimChange(col, f);
|
|
1665
|
+
},
|
|
1666
|
+
style: selectStyle2,
|
|
1667
|
+
children: [
|
|
1668
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "-- select dimension --" }),
|
|
1669
|
+
dimensions.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: f.name, children: [
|
|
1670
|
+
f.displayName,
|
|
1671
|
+
" (",
|
|
1672
|
+
f.type,
|
|
1673
|
+
") \\u2014 ",
|
|
1674
|
+
f.name
|
|
1675
|
+
] }, f.name))
|
|
1676
|
+
]
|
|
1677
|
+
}
|
|
1678
|
+
) })
|
|
1679
|
+
] }, col);
|
|
1680
|
+
}) })
|
|
1681
|
+
] }),
|
|
1682
|
+
hasTimeDim && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 10, display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
1683
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { style: { fontSize: 12, fontWeight: 600, color: "#374151" }, children: "Time Grain:" }),
|
|
1684
|
+
/* @__PURE__ */ jsxRuntime.jsxs("select", { value: grain, onChange: (e) => setGrain(e.target.value), style: { ...selectStyle2, width: "auto" }, children: [
|
|
1685
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: "Day" }),
|
|
1686
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: "Week" }),
|
|
1687
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "month", children: "Month" }),
|
|
1688
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "quarter", children: "Quarter" }),
|
|
1689
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "year", children: "Year" })
|
|
1690
|
+
] })
|
|
1691
|
+
] })
|
|
1692
|
+
] }),
|
|
1693
|
+
catalog.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
|
|
1694
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Metrics (Values)" }),
|
|
1695
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: [
|
|
1696
|
+
"Which metrics should be queried?",
|
|
1697
|
+
analysis.metricColumns.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1698
|
+
" (replaces: ",
|
|
1699
|
+
analysis.metricColumns.join(", "),
|
|
1284
1700
|
")"
|
|
1285
1701
|
] })
|
|
1286
1702
|
] }),
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1703
|
+
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) => {
|
|
1704
|
+
const isSel = selectedMetrics.some((s) => s.name === m.name);
|
|
1705
|
+
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: [
|
|
1706
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: isSel, onChange: () => handleToggleMetric(m), style: { margin: 0 } }),
|
|
1707
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500, color: "#111827" }, children: m.displayName }),
|
|
1708
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { style: { fontSize: 10, color: "#6366f1", fontFamily: "monospace" }, children: m.name }),
|
|
1709
|
+
m.category && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: "auto", fontSize: 10, color: "#9ca3af" }, children: m.category })
|
|
1710
|
+
] }, m.name);
|
|
1711
|
+
}) }),
|
|
1712
|
+
selectedMetrics.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 8, fontSize: 11, color: "#6b7280" }, children: [
|
|
1713
|
+
"Selected: ",
|
|
1714
|
+
selectedMetrics.map((m) => m.displayName).join(", ")
|
|
1715
|
+
] })
|
|
1716
|
+
] }),
|
|
1717
|
+
canGenerate && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: sectionStyle, children: [
|
|
1718
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Query Preview" }),
|
|
1719
|
+
/* @__PURE__ */ jsxRuntime.jsx("pre", { style: codePreviewStyle, children: `useSemanticQuery({${selectedMetrics.length > 0 ? `
|
|
1720
|
+
metrics: [${selectedMetrics.map((m) => `'${m.name}'`).join(", ")}],` : ""}${Array.from(dimMappings.values()).some(Boolean) ? `
|
|
1721
|
+
groupBy: [${Array.from(dimMappings.values()).filter(Boolean).map((f) => `'${f.name}'`).join(", ")}],` : ""}${hasTimeDim && grain ? `
|
|
1722
|
+
grain: '${grain}',` : ""}
|
|
1723
|
+
})` })
|
|
1724
|
+
] })
|
|
1725
|
+
] }),
|
|
1726
|
+
/* @__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" }) })
|
|
1727
|
+
] });
|
|
1728
|
+
}
|
|
1729
|
+
var _nextWidgetId = 1;
|
|
1730
|
+
function nextWidgetId() {
|
|
1731
|
+
return `w${_nextWidgetId++}`;
|
|
1732
|
+
}
|
|
1733
|
+
var WIDGET_TYPES = [
|
|
1734
|
+
{ type: "kpi", label: "KPI", icon: "#" },
|
|
1735
|
+
{ type: "line", label: "Line", icon: "\u2571" },
|
|
1736
|
+
{ type: "bar", label: "Bar", icon: "\u2581\u2583\u2585" },
|
|
1737
|
+
{ type: "area", label: "Area", icon: "\u25E2" },
|
|
1738
|
+
{ type: "pie", label: "Pie", icon: "\u25D4" },
|
|
1739
|
+
{ type: "table", label: "Table", icon: "\u2637" }
|
|
1740
|
+
];
|
|
1741
|
+
function BuildTab() {
|
|
1742
|
+
const { fields: catalog, isLoading: catalogLoading } = chunkT2C43AAL_cjs.useMetrics();
|
|
1743
|
+
const allMetrics = react.useMemo(() => catalog.filter((f) => f.type === "metric"), [catalog]);
|
|
1744
|
+
const allDimensions = react.useMemo(() => {
|
|
1745
|
+
const dims = catalog.filter((f) => f.type === "dimension" || f.type === "time_dimension");
|
|
1746
|
+
return dims.sort((a, b) => {
|
|
1747
|
+
if (a.name === "metric_time") return -1;
|
|
1748
|
+
if (b.name === "metric_time") return 1;
|
|
1749
|
+
return 0;
|
|
1750
|
+
});
|
|
1751
|
+
}, [catalog]);
|
|
1752
|
+
const [dashboardTitle, setDashboardTitle] = react.useState("Dashboard");
|
|
1753
|
+
const [layout, setLayout] = react.useState("dashboard");
|
|
1754
|
+
const [widgets, setWidgets] = react.useState([]);
|
|
1755
|
+
const [sharedFilters, setSharedFilters] = react.useState([]);
|
|
1756
|
+
const [catalogSearch, setCatalogSearch] = react.useState("");
|
|
1757
|
+
const [copied, setCopied] = react.useState(false);
|
|
1758
|
+
const [lovableSent, setLovableSent] = react.useState(false);
|
|
1759
|
+
const [previewOpen, setPreviewOpen] = react.useState(false);
|
|
1760
|
+
const [catalogWidth, setCatalogWidth] = react.useState(220);
|
|
1761
|
+
const catalogDragRef = react.useRef(null);
|
|
1762
|
+
const inIframe = react.useMemo(() => isRunningInIframe(), []);
|
|
1763
|
+
const handleCatalogDragStart = react.useCallback((e) => {
|
|
1764
|
+
e.preventDefault();
|
|
1765
|
+
catalogDragRef.current = { startX: e.clientX, startW: catalogWidth };
|
|
1766
|
+
const onMove = (ev) => {
|
|
1767
|
+
if (!catalogDragRef.current) return;
|
|
1768
|
+
const delta = ev.clientX - catalogDragRef.current.startX;
|
|
1769
|
+
setCatalogWidth(Math.max(160, Math.min(400, catalogDragRef.current.startW + delta)));
|
|
1770
|
+
};
|
|
1771
|
+
const onUp = () => {
|
|
1772
|
+
catalogDragRef.current = null;
|
|
1773
|
+
document.removeEventListener("mousemove", onMove);
|
|
1774
|
+
document.removeEventListener("mouseup", onUp);
|
|
1775
|
+
};
|
|
1776
|
+
document.addEventListener("mousemove", onMove);
|
|
1777
|
+
document.addEventListener("mouseup", onUp);
|
|
1778
|
+
}, [catalogWidth]);
|
|
1779
|
+
const addWidget = react.useCallback((type) => {
|
|
1780
|
+
const isTimeSeries = type === "line" || type === "area" || type === "kpi";
|
|
1781
|
+
const metricTime = allDimensions.find((d) => d.name === "metric_time");
|
|
1782
|
+
setWidgets((prev) => [...prev, {
|
|
1783
|
+
id: nextWidgetId(),
|
|
1784
|
+
title: `${WIDGET_TYPES.find((t) => t.type === type)?.label ?? "Widget"} ${prev.length + 1}`,
|
|
1785
|
+
type,
|
|
1786
|
+
metrics: [],
|
|
1787
|
+
groupBy: isTimeSeries && metricTime ? [metricTime] : [],
|
|
1788
|
+
grain: isTimeSeries && metricTime ? "month" : null
|
|
1789
|
+
}]);
|
|
1790
|
+
}, [allDimensions]);
|
|
1791
|
+
const removeWidget = react.useCallback((id) => {
|
|
1792
|
+
setWidgets((prev) => prev.filter((w) => w.id !== id));
|
|
1793
|
+
}, []);
|
|
1794
|
+
const updateWidget = react.useCallback((id, patch) => {
|
|
1795
|
+
setWidgets((prev) => prev.map((w) => w.id === id ? { ...w, ...patch } : w));
|
|
1796
|
+
}, []);
|
|
1797
|
+
const duplicateWidget = react.useCallback((id) => {
|
|
1798
|
+
setWidgets((prev) => {
|
|
1799
|
+
const source = prev.find((w) => w.id === id);
|
|
1800
|
+
if (!source) return prev;
|
|
1801
|
+
const copy = {
|
|
1802
|
+
...source,
|
|
1803
|
+
id: nextWidgetId(),
|
|
1804
|
+
title: `${source.title} (2)`,
|
|
1805
|
+
metrics: [...source.metrics],
|
|
1806
|
+
groupBy: [...source.groupBy]
|
|
1807
|
+
};
|
|
1808
|
+
const idx = prev.findIndex((w) => w.id === id);
|
|
1809
|
+
return [...prev.slice(0, idx + 1), copy, ...prev.slice(idx + 1)];
|
|
1810
|
+
});
|
|
1811
|
+
}, []);
|
|
1812
|
+
const addFilter = react.useCallback((dim) => {
|
|
1813
|
+
if (sharedFilters.some((f) => f.dimension.name === dim.name)) return;
|
|
1814
|
+
setSharedFilters((prev) => [...prev, {
|
|
1815
|
+
dimension: dim,
|
|
1816
|
+
label: dim.displayName,
|
|
1817
|
+
timePreset: dim.type === "time_dimension" ? "12m" : null
|
|
1818
|
+
}]);
|
|
1819
|
+
}, [sharedFilters]);
|
|
1820
|
+
const removeFilter = react.useCallback((name) => {
|
|
1821
|
+
setSharedFilters((prev) => prev.filter((f) => f.dimension.name !== name));
|
|
1822
|
+
}, []);
|
|
1823
|
+
const updateFilterPreset = react.useCallback((name, preset) => {
|
|
1824
|
+
setSharedFilters((prev) => prev.map(
|
|
1825
|
+
(f) => f.dimension.name === name ? { ...f, timePreset: preset } : f
|
|
1826
|
+
));
|
|
1827
|
+
}, []);
|
|
1828
|
+
const addFieldToWidget = react.useCallback((widgetId, field) => {
|
|
1829
|
+
setWidgets((prev) => prev.map((w) => {
|
|
1830
|
+
if (w.id !== widgetId) return w;
|
|
1831
|
+
if (field.type === "metric") {
|
|
1832
|
+
if (w.metrics.some((m) => m.name === field.name)) return w;
|
|
1833
|
+
return { ...w, metrics: [...w.metrics, field] };
|
|
1834
|
+
} else {
|
|
1835
|
+
if (w.groupBy.some((d) => d.name === field.name)) return w;
|
|
1836
|
+
return { ...w, groupBy: [...w.groupBy, field] };
|
|
1837
|
+
}
|
|
1838
|
+
}));
|
|
1839
|
+
}, []);
|
|
1840
|
+
const removeFieldFromWidget = react.useCallback((widgetId, fieldName, role) => {
|
|
1841
|
+
setWidgets((prev) => prev.map((w) => {
|
|
1842
|
+
if (w.id !== widgetId) return w;
|
|
1843
|
+
if (role === "metric") return { ...w, metrics: w.metrics.filter((m) => m.name !== fieldName) };
|
|
1844
|
+
return { ...w, groupBy: w.groupBy.filter((d) => d.name !== fieldName) };
|
|
1845
|
+
}));
|
|
1846
|
+
}, []);
|
|
1847
|
+
const reportConfig = react.useMemo(() => ({
|
|
1848
|
+
dashboardTitle,
|
|
1849
|
+
layout,
|
|
1850
|
+
widgets,
|
|
1851
|
+
sharedFilters
|
|
1852
|
+
}), [dashboardTitle, layout, widgets, sharedFilters]);
|
|
1853
|
+
const prompt = react.useMemo(() => generateReportPrompt(reportConfig), [reportConfig]);
|
|
1854
|
+
const canGenerate = widgets.length > 0 && widgets.some((w) => w.metrics.length > 0);
|
|
1855
|
+
const canLovable = canGenerate && canOpenInLovable(prompt);
|
|
1856
|
+
const handleCopy = react.useCallback(async () => {
|
|
1857
|
+
await copyToClipboard(prompt);
|
|
1858
|
+
setCopied(true);
|
|
1859
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1860
|
+
}, [prompt]);
|
|
1861
|
+
const handleLovable = react.useCallback(async () => {
|
|
1862
|
+
if (inIframe) {
|
|
1863
|
+
await copyToClipboard(prompt);
|
|
1864
|
+
setLovableSent(true);
|
|
1865
|
+
setTimeout(() => setLovableSent(false), 3e3);
|
|
1866
|
+
} else {
|
|
1867
|
+
openInLovable(prompt);
|
|
1868
|
+
setLovableSent(true);
|
|
1869
|
+
setTimeout(() => setLovableSent(false), 3e3);
|
|
1870
|
+
}
|
|
1871
|
+
}, [prompt, inIframe]);
|
|
1872
|
+
const filteredMetrics = react.useMemo(() => {
|
|
1873
|
+
const q = catalogSearch.toLowerCase();
|
|
1874
|
+
if (!q) return allMetrics;
|
|
1875
|
+
return allMetrics.filter((f) => f.displayName.toLowerCase().includes(q) || f.name.toLowerCase().includes(q) || f.description.toLowerCase().includes(q));
|
|
1876
|
+
}, [allMetrics, catalogSearch]);
|
|
1877
|
+
const filteredDimensions = react.useMemo(() => {
|
|
1878
|
+
const q = catalogSearch.toLowerCase();
|
|
1879
|
+
if (!q) return allDimensions;
|
|
1880
|
+
return allDimensions.filter((f) => f.displayName.toLowerCase().includes(q) || f.name.toLowerCase().includes(q) || f.description.toLowerCase().includes(q));
|
|
1881
|
+
}, [allDimensions, catalogSearch]);
|
|
1882
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1883
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { overflow: "auto", flex: 1, display: "flex", flexDirection: "column" }, children: [
|
|
1884
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...sectionStyle, display: "flex", gap: 12, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
1885
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1886
|
+
"input",
|
|
1887
|
+
{
|
|
1888
|
+
value: dashboardTitle,
|
|
1889
|
+
onChange: (e) => setDashboardTitle(e.target.value),
|
|
1890
|
+
placeholder: "Dashboard title",
|
|
1891
|
+
style: { ...inputStyle2, flex: 1, minWidth: 160, fontSize: 15, fontWeight: 700 }
|
|
1892
|
+
}
|
|
1893
|
+
),
|
|
1894
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 4 }, children: ["single", "two-column", "dashboard"].map((l) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1895
|
+
"button",
|
|
1896
|
+
{
|
|
1897
|
+
onClick: () => setLayout(l),
|
|
1898
|
+
style: {
|
|
1899
|
+
padding: "4px 10px",
|
|
1900
|
+
fontSize: 11,
|
|
1901
|
+
fontWeight: 600,
|
|
1902
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
1903
|
+
borderRadius: 4,
|
|
1904
|
+
border: layout === l ? "1px solid #3b82f6" : "1px solid #d1d5db",
|
|
1905
|
+
backgroundColor: layout === l ? "#eff6ff" : "#fff",
|
|
1906
|
+
color: layout === l ? "#3b82f6" : "#6b7280",
|
|
1907
|
+
cursor: "pointer"
|
|
1908
|
+
},
|
|
1909
|
+
children: l === "single" ? "1-Col" : l === "two-column" ? "2-Col" : "Dashboard"
|
|
1910
|
+
},
|
|
1911
|
+
l
|
|
1912
|
+
)) })
|
|
1913
|
+
] }),
|
|
1914
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flex: 1, minHeight: 0 }, children: [
|
|
1915
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: catalogWidth, borderRight: "1px solid #e5e7eb", overflow: "auto", flexShrink: 0 }, children: [
|
|
1916
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "8px 12px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1917
|
+
"input",
|
|
1918
|
+
{
|
|
1919
|
+
value: catalogSearch,
|
|
1920
|
+
onChange: (e) => setCatalogSearch(e.target.value),
|
|
1921
|
+
placeholder: "Search fields...",
|
|
1922
|
+
style: { ...inputStyle2, width: "100%", fontSize: 12 }
|
|
1923
|
+
}
|
|
1924
|
+
) }),
|
|
1925
|
+
catalogLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 12, fontSize: 12, color: "#9ca3af" }, children: "Loading catalog..." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1926
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "4px 12px" }, children: [
|
|
1927
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...sectionTitleStyle, marginBottom: 4 }, children: [
|
|
1928
|
+
"Metrics (",
|
|
1929
|
+
filteredMetrics.length,
|
|
1930
|
+
")"
|
|
1931
|
+
] }),
|
|
1932
|
+
filteredMetrics.map((f) => /* @__PURE__ */ jsxRuntime.jsx(CatalogChip, { field: f, widgets, onAddToWidget: addFieldToWidget }, f.name)),
|
|
1933
|
+
filteredMetrics.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", padding: "4px 0" }, children: "None found" })
|
|
1934
|
+
] }),
|
|
1935
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "4px 12px" }, children: [
|
|
1936
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...sectionTitleStyle, marginBottom: 4 }, children: [
|
|
1937
|
+
"Dimensions (",
|
|
1938
|
+
filteredDimensions.length,
|
|
1939
|
+
")"
|
|
1940
|
+
] }),
|
|
1941
|
+
filteredDimensions.map((f) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1942
|
+
CatalogChip,
|
|
1943
|
+
{
|
|
1944
|
+
field: f,
|
|
1945
|
+
widgets,
|
|
1946
|
+
onAddToWidget: addFieldToWidget,
|
|
1947
|
+
onAddAsFilter: addFilter,
|
|
1948
|
+
suffix: f.name === "metric_time" ? " (recommended)" : void 0
|
|
1949
|
+
},
|
|
1950
|
+
f.name
|
|
1951
|
+
)),
|
|
1952
|
+
filteredDimensions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", padding: "4px 0" }, children: "None found" })
|
|
1953
|
+
] })
|
|
1346
1954
|
] })
|
|
1347
1955
|
] }),
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
"
|
|
1356
|
-
|
|
1956
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1957
|
+
"div",
|
|
1958
|
+
{
|
|
1959
|
+
onMouseDown: handleCatalogDragStart,
|
|
1960
|
+
style: {
|
|
1961
|
+
width: 4,
|
|
1962
|
+
cursor: "col-resize",
|
|
1963
|
+
backgroundColor: "transparent",
|
|
1964
|
+
flexShrink: 0,
|
|
1965
|
+
position: "relative"
|
|
1966
|
+
},
|
|
1967
|
+
onMouseEnter: (e) => {
|
|
1968
|
+
e.currentTarget.style.backgroundColor = "#d1d5db";
|
|
1969
|
+
},
|
|
1970
|
+
onMouseLeave: (e) => {
|
|
1971
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
),
|
|
1975
|
+
/* @__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: [
|
|
1976
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 14, fontWeight: 500, marginBottom: 12 }, children: "Compose your dashboard" }),
|
|
1977
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 12, marginBottom: 20 }, children: "Add widgets below, then pick metrics and dimensions from the catalog on the left." }),
|
|
1978
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddWidgetBar, { onAdd: addWidget })
|
|
1979
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1980
|
+
widgets.map((w) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1981
|
+
WidgetCard,
|
|
1982
|
+
{
|
|
1983
|
+
widget: w,
|
|
1984
|
+
onUpdate: updateWidget,
|
|
1985
|
+
onRemove: removeWidget,
|
|
1986
|
+
onDuplicate: duplicateWidget,
|
|
1987
|
+
onRemoveField: removeFieldFromWidget,
|
|
1988
|
+
allDimensions
|
|
1989
|
+
},
|
|
1990
|
+
w.id
|
|
1991
|
+
)),
|
|
1992
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddWidgetBar, { onAdd: addWidget }),
|
|
1993
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 16, padding: "12px 0", borderTop: "1px solid #f3f4f6" }, children: [
|
|
1994
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: sectionTitleStyle, children: "Shared Filters" }),
|
|
1995
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, color: "#9ca3af", marginBottom: 8 }, children: "Dimensions that filter all widgets together. Add from the catalog sidebar." }),
|
|
1996
|
+
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", flexDirection: "column", gap: 8 }, children: sharedFilters.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1997
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: chipStyle, children: [
|
|
1998
|
+
f.label,
|
|
1999
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => removeFilter(f.dimension.name), style: chipRemoveStyle, children: "\xD7" })
|
|
2000
|
+
] }) }),
|
|
2001
|
+
f.dimension.type === "time_dimension" && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 4, marginTop: 4, marginLeft: 4 }, children: Object.keys(TIME_PRESET_LABELS).map((p) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2002
|
+
"button",
|
|
2003
|
+
{
|
|
2004
|
+
onClick: () => updateFilterPreset(f.dimension.name, p),
|
|
2005
|
+
style: {
|
|
2006
|
+
padding: "2px 8px",
|
|
2007
|
+
fontSize: 10,
|
|
2008
|
+
fontWeight: 600,
|
|
2009
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
2010
|
+
borderRadius: 999,
|
|
2011
|
+
border: f.timePreset === p ? "1px solid #3b82f6" : "1px solid #e5e7eb",
|
|
2012
|
+
backgroundColor: f.timePreset === p ? "#eff6ff" : "#fff",
|
|
2013
|
+
color: f.timePreset === p ? "#3b82f6" : "#9ca3af",
|
|
2014
|
+
cursor: "pointer",
|
|
2015
|
+
transition: "all 0.1s"
|
|
2016
|
+
},
|
|
2017
|
+
children: TIME_PRESET_LABELS[p]
|
|
2018
|
+
},
|
|
2019
|
+
p
|
|
2020
|
+
)) })
|
|
2021
|
+
] }, f.dimension.name)) })
|
|
1357
2022
|
] }),
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
"label",
|
|
2023
|
+
canGenerate && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: 12 }, children: [
|
|
2024
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2025
|
+
"button",
|
|
1362
2026
|
{
|
|
1363
|
-
|
|
1364
|
-
|
|
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
|
-
},
|
|
2027
|
+
onClick: () => setPreviewOpen(!previewOpen),
|
|
2028
|
+
style: { fontSize: 11, fontWeight: 600, color: "#6b7280", background: "none", border: "none", cursor: "pointer", padding: 0, fontFamily: "system-ui, -apple-system, sans-serif" },
|
|
1374
2029
|
children: [
|
|
1375
|
-
|
|
1376
|
-
|
|
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 })
|
|
2030
|
+
previewOpen ? "\u25BC" : "\u25B6",
|
|
2031
|
+
" Prompt Preview"
|
|
1387
2032
|
]
|
|
1388
|
-
}
|
|
1389
|
-
|
|
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(", ")
|
|
2033
|
+
}
|
|
2034
|
+
),
|
|
2035
|
+
previewOpen && /* @__PURE__ */ jsxRuntime.jsx("pre", { style: { ...codePreviewStyle, marginTop: 8, maxHeight: 200, overflow: "auto", fontSize: 10 }, children: prompt })
|
|
1395
2036
|
] })
|
|
1396
|
-
] })
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
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(
|
|
2037
|
+
] }) })
|
|
2038
|
+
] })
|
|
2039
|
+
] }),
|
|
2040
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "12px 20px", borderTop: "1px solid #e5e7eb", display: "flex", gap: 10 }, children: [
|
|
2041
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1417
2042
|
"button",
|
|
1418
2043
|
{
|
|
1419
|
-
onClick:
|
|
2044
|
+
onClick: handleCopy,
|
|
1420
2045
|
disabled: !canGenerate,
|
|
1421
2046
|
style: {
|
|
1422
|
-
...
|
|
2047
|
+
...secondaryBtnStyle,
|
|
2048
|
+
flex: 1,
|
|
1423
2049
|
opacity: canGenerate ? 1 : 0.5,
|
|
1424
2050
|
cursor: canGenerate ? "pointer" : "not-allowed"
|
|
1425
2051
|
},
|
|
1426
|
-
children: copied ? "Copied
|
|
2052
|
+
children: copied ? "Copied!" : "Copy Prompt"
|
|
1427
2053
|
}
|
|
1428
|
-
)
|
|
2054
|
+
),
|
|
2055
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2056
|
+
"button",
|
|
2057
|
+
{
|
|
2058
|
+
onClick: handleLovable,
|
|
2059
|
+
disabled: !canLovable,
|
|
2060
|
+
title: canGenerate && !canLovable ? "Prompt exceeds 50,000 character limit for direct send" : void 0,
|
|
2061
|
+
style: {
|
|
2062
|
+
...primaryBtnStyle,
|
|
2063
|
+
flex: 1.5,
|
|
2064
|
+
opacity: canLovable ? 1 : 0.4,
|
|
2065
|
+
cursor: canLovable ? "pointer" : "not-allowed",
|
|
2066
|
+
display: "flex",
|
|
2067
|
+
alignItems: "center",
|
|
2068
|
+
justifyContent: "center",
|
|
2069
|
+
gap: 6
|
|
2070
|
+
},
|
|
2071
|
+
children: [
|
|
2072
|
+
lovableSent ? inIframe ? "Copied! Paste into Lovable chat" : "Opening Lovable..." : inIframe ? "Send to Lovable \u2764\uFE0F" : "Build in Lovable \u2764\uFE0F",
|
|
2073
|
+
!lovableSent && !inIframe && /* @__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: [
|
|
2074
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 17L17 7" }),
|
|
2075
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M7 7h10v10" })
|
|
2076
|
+
] })
|
|
2077
|
+
]
|
|
2078
|
+
}
|
|
2079
|
+
)
|
|
1429
2080
|
] })
|
|
1430
2081
|
] });
|
|
1431
2082
|
}
|
|
2083
|
+
function CatalogChip({
|
|
2084
|
+
field,
|
|
2085
|
+
widgets,
|
|
2086
|
+
onAddToWidget,
|
|
2087
|
+
onAddAsFilter,
|
|
2088
|
+
suffix
|
|
2089
|
+
}) {
|
|
2090
|
+
const [menuOpen, setMenuOpen] = react.useState(false);
|
|
2091
|
+
const typeIcon = field.type === "metric" ? "\u2581" : field.type === "time_dimension" ? "\u25F7" : "\u2022";
|
|
2092
|
+
const typeColor = field.type === "metric" ? "#10b981" : field.type === "time_dimension" ? "#f59e0b" : "#6366f1";
|
|
2093
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
|
|
2094
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2095
|
+
"button",
|
|
2096
|
+
{
|
|
2097
|
+
onClick: () => {
|
|
2098
|
+
if (widgets.length === 1) {
|
|
2099
|
+
onAddToWidget(widgets[0].id, field);
|
|
2100
|
+
} else if (widgets.length > 1) {
|
|
2101
|
+
setMenuOpen(!menuOpen);
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
2104
|
+
title: `${field.displayName}
|
|
2105
|
+
${field.description}`,
|
|
2106
|
+
style: {
|
|
2107
|
+
display: "flex",
|
|
2108
|
+
alignItems: "center",
|
|
2109
|
+
gap: 4,
|
|
2110
|
+
width: "100%",
|
|
2111
|
+
textAlign: "left",
|
|
2112
|
+
padding: "3px 6px",
|
|
2113
|
+
fontSize: 11,
|
|
2114
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
2115
|
+
color: "#374151",
|
|
2116
|
+
backgroundColor: "transparent",
|
|
2117
|
+
border: "none",
|
|
2118
|
+
borderRadius: 3,
|
|
2119
|
+
cursor: widgets.length > 0 ? "pointer" : "default",
|
|
2120
|
+
opacity: widgets.length > 0 ? 1 : 0.6
|
|
2121
|
+
},
|
|
2122
|
+
onMouseEnter: (e) => {
|
|
2123
|
+
e.currentTarget.style.backgroundColor = "#f3f4f6";
|
|
2124
|
+
},
|
|
2125
|
+
onMouseLeave: (e) => {
|
|
2126
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2127
|
+
},
|
|
2128
|
+
children: [
|
|
2129
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: typeColor, fontSize: 10, flexShrink: 0 }, children: typeIcon }),
|
|
2130
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
|
|
2131
|
+
field.displayName,
|
|
2132
|
+
suffix && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#9ca3af", fontWeight: 400 }, children: suffix })
|
|
2133
|
+
] })
|
|
2134
|
+
]
|
|
2135
|
+
}
|
|
2136
|
+
),
|
|
2137
|
+
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: [
|
|
2138
|
+
widgets.map((w) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2139
|
+
"button",
|
|
2140
|
+
{
|
|
2141
|
+
onClick: () => {
|
|
2142
|
+
onAddToWidget(w.id, field);
|
|
2143
|
+
setMenuOpen(false);
|
|
2144
|
+
},
|
|
2145
|
+
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" },
|
|
2146
|
+
onMouseEnter: (e) => {
|
|
2147
|
+
e.currentTarget.style.backgroundColor = "#f3f4f6";
|
|
2148
|
+
},
|
|
2149
|
+
onMouseLeave: (e) => {
|
|
2150
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2151
|
+
},
|
|
2152
|
+
children: w.title
|
|
2153
|
+
},
|
|
2154
|
+
w.id
|
|
2155
|
+
)),
|
|
2156
|
+
onAddAsFilter && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2157
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderTop: "1px solid #e5e7eb", margin: "4px 0" } }),
|
|
2158
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2159
|
+
"button",
|
|
2160
|
+
{
|
|
2161
|
+
onClick: () => {
|
|
2162
|
+
onAddAsFilter(field);
|
|
2163
|
+
setMenuOpen(false);
|
|
2164
|
+
},
|
|
2165
|
+
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" },
|
|
2166
|
+
onMouseEnter: (e) => {
|
|
2167
|
+
e.currentTarget.style.backgroundColor = "#f3f4f6";
|
|
2168
|
+
},
|
|
2169
|
+
onMouseLeave: (e) => {
|
|
2170
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
2171
|
+
},
|
|
2172
|
+
children: "+ Add as shared filter"
|
|
2173
|
+
}
|
|
2174
|
+
)
|
|
2175
|
+
] })
|
|
2176
|
+
] })
|
|
2177
|
+
] });
|
|
2178
|
+
}
|
|
2179
|
+
function AddWidgetBar({ onAdd }) {
|
|
2180
|
+
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(
|
|
2181
|
+
"button",
|
|
2182
|
+
{
|
|
2183
|
+
onClick: () => onAdd(wt.type),
|
|
2184
|
+
style: {
|
|
2185
|
+
display: "flex",
|
|
2186
|
+
flexDirection: "column",
|
|
2187
|
+
alignItems: "center",
|
|
2188
|
+
gap: 2,
|
|
2189
|
+
padding: "6px 12px",
|
|
2190
|
+
fontSize: 10,
|
|
2191
|
+
fontWeight: 600,
|
|
2192
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
2193
|
+
color: "#6b7280",
|
|
2194
|
+
backgroundColor: "#fff",
|
|
2195
|
+
border: "1px dashed #d1d5db",
|
|
2196
|
+
borderRadius: 6,
|
|
2197
|
+
cursor: "pointer",
|
|
2198
|
+
transition: "all 0.1s",
|
|
2199
|
+
minWidth: 56
|
|
2200
|
+
},
|
|
2201
|
+
onMouseEnter: (e) => {
|
|
2202
|
+
const el = e.currentTarget;
|
|
2203
|
+
el.style.borderColor = "#3b82f6";
|
|
2204
|
+
el.style.color = "#3b82f6";
|
|
2205
|
+
},
|
|
2206
|
+
onMouseLeave: (e) => {
|
|
2207
|
+
const el = e.currentTarget;
|
|
2208
|
+
el.style.borderColor = "#d1d5db";
|
|
2209
|
+
el.style.color = "#6b7280";
|
|
2210
|
+
},
|
|
2211
|
+
children: [
|
|
2212
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 16, lineHeight: 1 }, children: wt.icon }),
|
|
2213
|
+
wt.label
|
|
2214
|
+
]
|
|
2215
|
+
},
|
|
2216
|
+
wt.type
|
|
2217
|
+
)) });
|
|
2218
|
+
}
|
|
2219
|
+
function WidgetCard({
|
|
2220
|
+
widget,
|
|
2221
|
+
onUpdate,
|
|
2222
|
+
onRemove,
|
|
2223
|
+
onDuplicate,
|
|
2224
|
+
onRemoveField,
|
|
2225
|
+
allDimensions
|
|
2226
|
+
}) {
|
|
2227
|
+
const [collapsed, setCollapsed] = react.useState(false);
|
|
2228
|
+
const hasTimeDim = widget.groupBy.some((d) => d.type === "time_dimension");
|
|
2229
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: "1px solid #e5e7eb", borderRadius: 8, marginBottom: 10, backgroundColor: "#fff" }, children: [
|
|
2230
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "8px 12px", borderBottom: collapsed ? "none" : "1px solid #f3f4f6" }, children: [
|
|
2231
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setCollapsed(!collapsed), style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontSize: 10, color: "#9ca3af" }, children: collapsed ? "\u25B6" : "\u25BC" }),
|
|
2232
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2233
|
+
"input",
|
|
2234
|
+
{
|
|
2235
|
+
value: widget.title,
|
|
2236
|
+
onChange: (e) => onUpdate(widget.id, { title: e.target.value }),
|
|
2237
|
+
style: { ...inputStyle2, flex: 1, fontSize: 13, fontWeight: 600, padding: "2px 4px" }
|
|
2238
|
+
}
|
|
2239
|
+
),
|
|
2240
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 2 }, children: WIDGET_TYPES.map((wt) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2241
|
+
"button",
|
|
2242
|
+
{
|
|
2243
|
+
onClick: () => onUpdate(widget.id, { type: wt.type }),
|
|
2244
|
+
title: wt.label,
|
|
2245
|
+
style: {
|
|
2246
|
+
width: 24,
|
|
2247
|
+
height: 24,
|
|
2248
|
+
display: "flex",
|
|
2249
|
+
alignItems: "center",
|
|
2250
|
+
justifyContent: "center",
|
|
2251
|
+
fontSize: 12,
|
|
2252
|
+
borderRadius: 4,
|
|
2253
|
+
border: widget.type === wt.type ? "1px solid #3b82f6" : "1px solid transparent",
|
|
2254
|
+
backgroundColor: widget.type === wt.type ? "#eff6ff" : "transparent",
|
|
2255
|
+
color: widget.type === wt.type ? "#3b82f6" : "#9ca3af",
|
|
2256
|
+
cursor: "pointer"
|
|
2257
|
+
},
|
|
2258
|
+
children: wt.icon
|
|
2259
|
+
},
|
|
2260
|
+
wt.type
|
|
2261
|
+
)) }),
|
|
2262
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onDuplicate(widget.id), style: { background: "none", border: "none", cursor: "pointer", fontSize: 12, color: "#d1d5db", padding: "0 2px" }, title: "Duplicate widget", children: "\u2398" }),
|
|
2263
|
+
/* @__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" })
|
|
2264
|
+
] }),
|
|
2265
|
+
!collapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "8px 12px" }, children: [
|
|
2266
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 8 }, children: [
|
|
2267
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 10, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 4 }, children: "Metrics" }),
|
|
2268
|
+
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: [
|
|
2269
|
+
m.displayName,
|
|
2270
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onRemoveField(widget.id, m.name, "metric"), style: chipRemoveStyle, children: "\xD7" })
|
|
2271
|
+
] }, m.name)) })
|
|
2272
|
+
] }),
|
|
2273
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 8 }, children: [
|
|
2274
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 10, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 4 }, children: "Group By" }),
|
|
2275
|
+
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: [
|
|
2276
|
+
d.displayName,
|
|
2277
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => onRemoveField(widget.id, d.name, "groupBy"), style: chipRemoveStyle, children: "\xD7" })
|
|
2278
|
+
] }, d.name)) })
|
|
2279
|
+
] }),
|
|
2280
|
+
hasTimeDim && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
|
|
2281
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 10, fontWeight: 600, color: "#6b7280", textTransform: "uppercase" }, children: "Grain" }),
|
|
2282
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2283
|
+
"select",
|
|
2284
|
+
{
|
|
2285
|
+
value: widget.grain ?? "",
|
|
2286
|
+
onChange: (e) => onUpdate(widget.id, { grain: e.target.value || null }),
|
|
2287
|
+
style: { ...selectStyle2, width: "auto", fontSize: 11 },
|
|
2288
|
+
children: [
|
|
2289
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "None" }),
|
|
2290
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: "Day" }),
|
|
2291
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: "Week" }),
|
|
2292
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "month", children: "Month" }),
|
|
2293
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "quarter", children: "Quarter" }),
|
|
2294
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "year", children: "Year" })
|
|
2295
|
+
]
|
|
2296
|
+
}
|
|
2297
|
+
)
|
|
2298
|
+
] })
|
|
2299
|
+
] })
|
|
2300
|
+
] });
|
|
2301
|
+
}
|
|
2302
|
+
async function copyToClipboard(text) {
|
|
2303
|
+
try {
|
|
2304
|
+
await navigator.clipboard.writeText(text);
|
|
2305
|
+
} catch {
|
|
2306
|
+
const ta = document.createElement("textarea");
|
|
2307
|
+
ta.value = text;
|
|
2308
|
+
document.body.appendChild(ta);
|
|
2309
|
+
ta.select();
|
|
2310
|
+
document.execCommand("copy");
|
|
2311
|
+
document.body.removeChild(ta);
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
1432
2314
|
var backdropStyle = {
|
|
1433
2315
|
position: "fixed",
|
|
1434
2316
|
inset: 0,
|
|
@@ -1436,12 +2318,9 @@ var backdropStyle = {
|
|
|
1436
2318
|
zIndex: 1e5
|
|
1437
2319
|
};
|
|
1438
2320
|
var modalStyle = {
|
|
1439
|
-
position: "fixed",
|
|
1440
|
-
top: "50%",
|
|
1441
|
-
left: "50%",
|
|
1442
|
-
transform: "translate(-50%, -50%)",
|
|
1443
2321
|
zIndex: 100001,
|
|
1444
|
-
width: "min(
|
|
2322
|
+
width: "min(900px, 95vw)",
|
|
2323
|
+
height: "85vh",
|
|
1445
2324
|
maxHeight: "85vh",
|
|
1446
2325
|
backgroundColor: "#fff",
|
|
1447
2326
|
borderRadius: 12,
|
|
@@ -1454,8 +2333,8 @@ var modalStyle = {
|
|
|
1454
2333
|
var modalHeaderStyle = {
|
|
1455
2334
|
display: "flex",
|
|
1456
2335
|
justifyContent: "space-between",
|
|
1457
|
-
alignItems: "
|
|
1458
|
-
padding: "
|
|
2336
|
+
alignItems: "center",
|
|
2337
|
+
padding: "0 20px",
|
|
1459
2338
|
borderBottom: "1px solid #e5e7eb"
|
|
1460
2339
|
};
|
|
1461
2340
|
var closeBtnStyle = {
|
|
@@ -1471,7 +2350,7 @@ var sectionStyle = {
|
|
|
1471
2350
|
padding: "12px 20px"
|
|
1472
2351
|
};
|
|
1473
2352
|
var sectionTitleStyle = {
|
|
1474
|
-
fontSize:
|
|
2353
|
+
fontSize: 10,
|
|
1475
2354
|
fontWeight: 700,
|
|
1476
2355
|
textTransform: "uppercase",
|
|
1477
2356
|
letterSpacing: "0.05em",
|
|
@@ -1515,7 +2394,6 @@ var mappingTdStyle = {
|
|
|
1515
2394
|
verticalAlign: "middle"
|
|
1516
2395
|
};
|
|
1517
2396
|
var selectStyle2 = {
|
|
1518
|
-
width: "100%",
|
|
1519
2397
|
padding: "4px 6px",
|
|
1520
2398
|
fontSize: 12,
|
|
1521
2399
|
border: "1px solid #d1d5db",
|
|
@@ -1523,15 +2401,67 @@ var selectStyle2 = {
|
|
|
1523
2401
|
backgroundColor: "#fff",
|
|
1524
2402
|
color: "#111827"
|
|
1525
2403
|
};
|
|
1526
|
-
var
|
|
1527
|
-
|
|
2404
|
+
var inputStyle2 = {
|
|
2405
|
+
padding: "4px 8px",
|
|
2406
|
+
fontSize: 12,
|
|
2407
|
+
border: "1px solid #d1d5db",
|
|
2408
|
+
borderRadius: 4,
|
|
2409
|
+
backgroundColor: "#fff",
|
|
2410
|
+
color: "#111827",
|
|
2411
|
+
outline: "none",
|
|
2412
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
2413
|
+
};
|
|
2414
|
+
var codePreviewStyle = {
|
|
2415
|
+
fontSize: 11,
|
|
2416
|
+
fontFamily: "monospace",
|
|
2417
|
+
backgroundColor: "#f9fafb",
|
|
2418
|
+
border: "1px solid #e5e7eb",
|
|
2419
|
+
borderRadius: 6,
|
|
2420
|
+
padding: "8px 12px",
|
|
2421
|
+
whiteSpace: "pre-wrap",
|
|
2422
|
+
color: "#374151",
|
|
2423
|
+
margin: 0
|
|
2424
|
+
};
|
|
2425
|
+
var primaryBtnStyle = {
|
|
1528
2426
|
padding: "10px 16px",
|
|
1529
2427
|
fontSize: 14,
|
|
1530
2428
|
fontWeight: 600,
|
|
1531
2429
|
color: "#fff",
|
|
1532
2430
|
backgroundColor: "#3b82f6",
|
|
1533
2431
|
border: "none",
|
|
1534
|
-
borderRadius: 8
|
|
2432
|
+
borderRadius: 8,
|
|
2433
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
2434
|
+
};
|
|
2435
|
+
var secondaryBtnStyle = {
|
|
2436
|
+
padding: "10px 16px",
|
|
2437
|
+
fontSize: 14,
|
|
2438
|
+
fontWeight: 600,
|
|
2439
|
+
color: "#374151",
|
|
2440
|
+
backgroundColor: "#fff",
|
|
2441
|
+
border: "1px solid #d1d5db",
|
|
2442
|
+
borderRadius: 8,
|
|
2443
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
2444
|
+
};
|
|
2445
|
+
var chipStyle = {
|
|
2446
|
+
display: "inline-flex",
|
|
2447
|
+
alignItems: "center",
|
|
2448
|
+
gap: 4,
|
|
2449
|
+
padding: "2px 8px",
|
|
2450
|
+
fontSize: 11,
|
|
2451
|
+
fontWeight: 500,
|
|
2452
|
+
borderRadius: 999,
|
|
2453
|
+
border: "1px solid #e5e7eb",
|
|
2454
|
+
backgroundColor: "#f9fafb",
|
|
2455
|
+
color: "#374151"
|
|
2456
|
+
};
|
|
2457
|
+
var chipRemoveStyle = {
|
|
2458
|
+
background: "none",
|
|
2459
|
+
border: "none",
|
|
2460
|
+
cursor: "pointer",
|
|
2461
|
+
fontSize: 12,
|
|
2462
|
+
color: "#9ca3af",
|
|
2463
|
+
padding: 0,
|
|
2464
|
+
lineHeight: 1
|
|
1535
2465
|
};
|
|
1536
2466
|
|
|
1537
2467
|
exports.AdminDashboard = AdminDashboard;
|
|
@@ -1541,8 +2471,12 @@ exports.Inspectable = Inspectable;
|
|
|
1541
2471
|
exports.MetricPicker = MetricPicker;
|
|
1542
2472
|
exports.ResultsTable = ResultsTable;
|
|
1543
2473
|
exports.analyzeChartData = analyzeChartData;
|
|
2474
|
+
exports.canOpenInLovable = canOpenInLovable;
|
|
1544
2475
|
exports.generateMigrationPrompt = generateMigrationPrompt;
|
|
2476
|
+
exports.generateReportPrompt = generateReportPrompt;
|
|
2477
|
+
exports.isRunningInIframe = isRunningInIframe;
|
|
1545
2478
|
exports.matchField = matchField;
|
|
1546
2479
|
exports.matchFields = matchFields;
|
|
2480
|
+
exports.openInLovable = openInLovable;
|
|
1547
2481
|
//# sourceMappingURL=components.cjs.map
|
|
1548
2482
|
//# sourceMappingURL=components.cjs.map
|