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