@inkindcards/semantic-layer 2.2.6 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components.cjs +1060 -162
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.cts +61 -5
- package/dist/components.d.ts +61 -5
- package/dist/components.js +1056 -163
- 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
|
}
|
|
@@ -996,51 +999,309 @@ function matchDisplayName(column, catalog) {
|
|
|
996
999
|
if (lower.length < 3) return null;
|
|
997
1000
|
return catalog.find((f) => f.displayName.toLowerCase().includes(lower)) ?? catalog.find((f) => f.description.toLowerCase().includes(lower)) ?? null;
|
|
998
1001
|
}
|
|
1002
|
+
function matchField(column, catalog) {
|
|
1003
|
+
const exact = matchExact(column, catalog);
|
|
1004
|
+
if (exact) return { field: exact, confidence: "exact" };
|
|
1005
|
+
const normalized = matchNormalized(column, catalog);
|
|
1006
|
+
if (normalized) return { field: normalized, confidence: "fuzzy" };
|
|
1007
|
+
const display = matchDisplayName(column, catalog);
|
|
1008
|
+
if (display) return { field: display, confidence: "fuzzy" };
|
|
1009
|
+
return { field: null, confidence: "manual" };
|
|
1010
|
+
}
|
|
999
1011
|
function matchFields(columns, catalog) {
|
|
1000
1012
|
return columns.map((column) => {
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
const normalized = matchNormalized(column, catalog);
|
|
1004
|
-
if (normalized) return { column, suggestedField: normalized, confidence: "fuzzy" };
|
|
1005
|
-
const display = matchDisplayName(column, catalog);
|
|
1006
|
-
if (display) return { column, suggestedField: display, confidence: "fuzzy" };
|
|
1007
|
-
return { column, suggestedField: null, confidence: "manual" };
|
|
1013
|
+
const { field, confidence } = matchField(column, catalog);
|
|
1014
|
+
return { column, suggestedField: field, confidence };
|
|
1008
1015
|
});
|
|
1009
1016
|
}
|
|
1017
|
+
function analyzeChartData(columns, sampleValues) {
|
|
1018
|
+
const dimensionColumns = [];
|
|
1019
|
+
const metricColumns = [];
|
|
1020
|
+
for (const col of columns) {
|
|
1021
|
+
const values = sampleValues.map((row) => row[col]).filter((v) => v !== null && v !== void 0);
|
|
1022
|
+
if (values.length === 0) {
|
|
1023
|
+
dimensionColumns.push(col);
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
const numericCount = values.filter((v) => typeof v === "number" || typeof v === "string" && !isNaN(Number(v)) && v.trim() !== "").length;
|
|
1027
|
+
const isNumeric = numericCount / values.length > 0.5;
|
|
1028
|
+
if (isNumeric) {
|
|
1029
|
+
metricColumns.push(col);
|
|
1030
|
+
} else {
|
|
1031
|
+
dimensionColumns.push(col);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return { dimensionColumns, metricColumns };
|
|
1035
|
+
}
|
|
1010
1036
|
|
|
1011
1037
|
// src/components/prompt-generator.ts
|
|
1012
|
-
function generateMigrationPrompt(
|
|
1013
|
-
const
|
|
1014
|
-
if (
|
|
1038
|
+
function generateMigrationPrompt(config) {
|
|
1039
|
+
const { componentLabel, dimensions, metrics, grain, originalMetricColumns } = config;
|
|
1040
|
+
if (dimensions.length === 0 && metrics.length === 0) {
|
|
1015
1041
|
return `Replace the sample/hardcoded data in the "${componentLabel}" component with live data from the semantic layer using useSemanticQuery. Use the DataCatalog to find the right field names.`;
|
|
1016
1042
|
}
|
|
1017
|
-
const metrics = resolved.filter((m) => m.field.type === "metric").map((m) => m.field.name);
|
|
1018
|
-
const timeDims = resolved.filter((m) => m.field.type === "time_dimension").map((m) => m.field.name);
|
|
1019
|
-
const dims = resolved.filter((m) => m.field.type === "dimension").map((m) => m.field.name);
|
|
1020
|
-
const groupBy = [...timeDims, ...dims];
|
|
1021
1043
|
const parts = [
|
|
1022
|
-
`Replace the sample/hardcoded data in the "${componentLabel}" component with a useSemanticQuery call using
|
|
1044
|
+
`Replace the sample/hardcoded data in the "${componentLabel}" component with a useSemanticQuery call using:`,
|
|
1045
|
+
""
|
|
1023
1046
|
];
|
|
1024
1047
|
if (metrics.length > 0) {
|
|
1025
|
-
parts.push(` metrics: [${metrics.map((
|
|
1048
|
+
parts.push(` metrics: [${metrics.map((m) => `'${m.name}'`).join(", ")}]`);
|
|
1026
1049
|
}
|
|
1027
|
-
if (
|
|
1050
|
+
if (dimensions.length > 0) {
|
|
1051
|
+
const groupBy = dimensions.map((d) => d.field.name);
|
|
1028
1052
|
parts.push(` groupBy: [${groupBy.map((n) => `'${n}'`).join(", ")}]`);
|
|
1029
1053
|
}
|
|
1030
|
-
if (
|
|
1031
|
-
parts.push(` grain: '
|
|
1054
|
+
if (grain) {
|
|
1055
|
+
parts.push(` grain: '${grain}'`);
|
|
1032
1056
|
}
|
|
1033
1057
|
parts.push("");
|
|
1034
|
-
|
|
1035
|
-
for (const
|
|
1036
|
-
|
|
1058
|
+
const sampleRow = [];
|
|
1059
|
+
for (const d of dimensions) {
|
|
1060
|
+
sampleRow.push(`${d.field.name}: "..."`);
|
|
1061
|
+
}
|
|
1062
|
+
for (const m of metrics) {
|
|
1063
|
+
sampleRow.push(`${m.name}: 1234`);
|
|
1064
|
+
}
|
|
1065
|
+
parts.push("The query returns rows like:");
|
|
1066
|
+
parts.push(` { ${sampleRow.join(", ")} }`);
|
|
1067
|
+
parts.push("");
|
|
1068
|
+
parts.push("Map to the chart's expected format:");
|
|
1069
|
+
for (const d of dimensions) {
|
|
1070
|
+
parts.push(` X-axis / category: "${d.field.name}" (was "${d.column}" in the hardcoded data)`);
|
|
1071
|
+
}
|
|
1072
|
+
if (metrics.length > 0 && originalMetricColumns.length > 0) {
|
|
1073
|
+
parts.push(` Series / values: each metric (${metrics.map((m) => `"${m.name}"`).join(", ")}) becomes a line/area/bar in the chart`);
|
|
1074
|
+
parts.push(` (replaces the old columns: ${originalMetricColumns.map((c) => `"${c}"`).join(", ")})`);
|
|
1037
1075
|
}
|
|
1038
1076
|
parts.push("");
|
|
1039
1077
|
parts.push(
|
|
1040
|
-
"Remove the old hardcoded/sample data and any fetch logic it used. Use the data, isLoading, and error values from useSemanticQuery to render the component."
|
|
1078
|
+
"Remove the old hardcoded/sample data and any fetch logic it used. Use the data, isLoading, and error values from useSemanticQuery to render the component. Show a loading spinner while isLoading is true. Show an error message if error is not null."
|
|
1041
1079
|
);
|
|
1042
1080
|
return parts.join("\n");
|
|
1043
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
|
+
}
|
|
1044
1305
|
function DataInspector({ children }) {
|
|
1045
1306
|
return /* @__PURE__ */ jsxs(InspectableRegistry, { children: [
|
|
1046
1307
|
/* @__PURE__ */ jsx(InspectorOverlay, {}),
|
|
@@ -1050,7 +1311,15 @@ function DataInspector({ children }) {
|
|
|
1050
1311
|
function InspectorOverlay() {
|
|
1051
1312
|
const registry = useContext(InspectableRegistryContext);
|
|
1052
1313
|
if (!registry) return null;
|
|
1053
|
-
const {
|
|
1314
|
+
const {
|
|
1315
|
+
inspectActive,
|
|
1316
|
+
setInspectActive,
|
|
1317
|
+
selectedId,
|
|
1318
|
+
setSelectedId,
|
|
1319
|
+
entries,
|
|
1320
|
+
modalTab,
|
|
1321
|
+
setModalTab
|
|
1322
|
+
} = registry;
|
|
1054
1323
|
useEffect(() => {
|
|
1055
1324
|
function handleKeyDown(e) {
|
|
1056
1325
|
if (e.ctrlKey && e.shiftKey && e.key === "I") {
|
|
@@ -1061,38 +1330,60 @@ function InspectorOverlay() {
|
|
|
1061
1330
|
window.addEventListener("keydown", handleKeyDown);
|
|
1062
1331
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
1063
1332
|
}, [inspectActive, setInspectActive]);
|
|
1333
|
+
useEffect(() => {
|
|
1334
|
+
if (selectedId) setModalTab("migrate");
|
|
1335
|
+
}, [selectedId, setModalTab]);
|
|
1064
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]);
|
|
1065
1354
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1066
1355
|
/* @__PURE__ */ jsx(
|
|
1067
1356
|
ToggleButton,
|
|
1068
1357
|
{
|
|
1069
1358
|
active: inspectActive,
|
|
1359
|
+
modalOpen: modalTab !== null,
|
|
1070
1360
|
count: entries.size,
|
|
1071
|
-
onToggle:
|
|
1072
|
-
setInspectActive(!inspectActive);
|
|
1073
|
-
if (inspectActive) setSelectedId(null);
|
|
1074
|
-
}
|
|
1361
|
+
onToggle: handleToggle
|
|
1075
1362
|
}
|
|
1076
1363
|
),
|
|
1077
|
-
|
|
1078
|
-
|
|
1364
|
+
modalTab && /* @__PURE__ */ jsx(
|
|
1365
|
+
UnifiedModal,
|
|
1079
1366
|
{
|
|
1367
|
+
tab: modalTab,
|
|
1368
|
+
onTabChange: handleTabChange,
|
|
1080
1369
|
entry: selectedEntry,
|
|
1081
|
-
onClose:
|
|
1370
|
+
onClose: handleClose
|
|
1082
1371
|
}
|
|
1083
1372
|
)
|
|
1084
1373
|
] });
|
|
1085
1374
|
}
|
|
1086
1375
|
function ToggleButton({
|
|
1087
1376
|
active,
|
|
1377
|
+
modalOpen,
|
|
1088
1378
|
count,
|
|
1089
1379
|
onToggle
|
|
1090
1380
|
}) {
|
|
1381
|
+
const lit = active || modalOpen;
|
|
1091
1382
|
return /* @__PURE__ */ jsxs(
|
|
1092
1383
|
"button",
|
|
1093
1384
|
{
|
|
1094
1385
|
onClick: onToggle,
|
|
1095
|
-
title:
|
|
1386
|
+
title: lit ? "Close data tools (Ctrl+Shift+I)" : "Open data tools (Ctrl+Shift+I)",
|
|
1096
1387
|
style: {
|
|
1097
1388
|
position: "fixed",
|
|
1098
1389
|
bottom: 20,
|
|
@@ -1105,9 +1396,9 @@ function ToggleButton({
|
|
|
1105
1396
|
fontSize: 13,
|
|
1106
1397
|
fontWeight: 600,
|
|
1107
1398
|
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
1108
|
-
color:
|
|
1109
|
-
backgroundColor:
|
|
1110
|
-
border: `1px solid ${
|
|
1399
|
+
color: lit ? "#fff" : "#374151",
|
|
1400
|
+
backgroundColor: lit ? "#3b82f6" : "#fff",
|
|
1401
|
+
border: `1px solid ${lit ? "#2563eb" : "#d1d5db"}`,
|
|
1111
1402
|
borderRadius: 999,
|
|
1112
1403
|
cursor: "pointer",
|
|
1113
1404
|
boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
|
|
@@ -1118,8 +1409,8 @@ function ToggleButton({
|
|
|
1118
1409
|
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
1119
1410
|
/* @__PURE__ */ jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
|
|
1120
1411
|
] }),
|
|
1121
|
-
|
|
1122
|
-
/* @__PURE__ */ jsx(
|
|
1412
|
+
lit ? "Active" : "Data Tools",
|
|
1413
|
+
count > 0 && /* @__PURE__ */ jsx(
|
|
1123
1414
|
"span",
|
|
1124
1415
|
{
|
|
1125
1416
|
style: {
|
|
@@ -1131,8 +1422,8 @@ function ToggleButton({
|
|
|
1131
1422
|
fontSize: 11,
|
|
1132
1423
|
fontWeight: 700,
|
|
1133
1424
|
borderRadius: 999,
|
|
1134
|
-
backgroundColor:
|
|
1135
|
-
color:
|
|
1425
|
+
backgroundColor: lit ? "rgba(255,255,255,0.25)" : "#e5e7eb",
|
|
1426
|
+
color: lit ? "#fff" : "#6b7280",
|
|
1136
1427
|
padding: "0 5px"
|
|
1137
1428
|
},
|
|
1138
1429
|
children: count
|
|
@@ -1142,154 +1433,708 @@ function ToggleButton({
|
|
|
1142
1433
|
}
|
|
1143
1434
|
);
|
|
1144
1435
|
}
|
|
1145
|
-
function
|
|
1436
|
+
function UnifiedModal({
|
|
1437
|
+
tab,
|
|
1438
|
+
onTabChange,
|
|
1146
1439
|
entry,
|
|
1147
1440
|
onClose
|
|
1148
1441
|
}) {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
+
}
|
|
1153
1475
|
);
|
|
1154
|
-
|
|
1155
|
-
|
|
1476
|
+
}
|
|
1477
|
+
function MigrateTab({
|
|
1478
|
+
entry,
|
|
1479
|
+
onClose: _onClose
|
|
1480
|
+
}) {
|
|
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
|
+
}) {
|
|
1496
|
+
const analysis = useMemo(
|
|
1497
|
+
() => analyzeChartData(entry.columns, entry.sampleValues),
|
|
1498
|
+
[entry.columns, entry.sampleValues]
|
|
1156
1499
|
);
|
|
1500
|
+
const dimensions = catalog.filter((f) => f.type === "dimension" || f.type === "time_dimension");
|
|
1501
|
+
const metrics = catalog.filter((f) => f.type === "metric");
|
|
1502
|
+
const initialDimMappings = useMemo(() => {
|
|
1503
|
+
const map = /* @__PURE__ */ new Map();
|
|
1504
|
+
for (const col of analysis.dimensionColumns) {
|
|
1505
|
+
const { field } = matchField(col, dimensions);
|
|
1506
|
+
map.set(col, field);
|
|
1507
|
+
}
|
|
1508
|
+
return map;
|
|
1509
|
+
}, [analysis.dimensionColumns, dimensions]);
|
|
1510
|
+
const [dimMappings, setDimMappings] = useState(initialDimMappings);
|
|
1157
1511
|
useEffect(() => {
|
|
1158
|
-
|
|
1159
|
-
}, [
|
|
1512
|
+
setDimMappings(initialDimMappings);
|
|
1513
|
+
}, [initialDimMappings]);
|
|
1514
|
+
const [selectedMetrics, setSelectedMetrics] = useState([]);
|
|
1515
|
+
const hasTimeDim = Array.from(dimMappings.values()).some((f) => f?.type === "time_dimension");
|
|
1516
|
+
const [grain, setGrain] = useState("month");
|
|
1160
1517
|
const [copied, setCopied] = useState(false);
|
|
1161
|
-
const
|
|
1162
|
-
|
|
1518
|
+
const handleDimChange = useCallback((col, field) => {
|
|
1519
|
+
setDimMappings((prev) => {
|
|
1163
1520
|
const next = new Map(prev);
|
|
1164
|
-
next.set(
|
|
1521
|
+
next.set(col, field);
|
|
1165
1522
|
return next;
|
|
1166
1523
|
});
|
|
1167
1524
|
}, []);
|
|
1525
|
+
const handleToggleMetric = useCallback((field) => {
|
|
1526
|
+
setSelectedMetrics(
|
|
1527
|
+
(prev) => prev.some((m) => m.name === field.name) ? prev.filter((m) => m.name !== field.name) : [...prev, field]
|
|
1528
|
+
);
|
|
1529
|
+
}, []);
|
|
1168
1530
|
const handleGenerate = useCallback(async () => {
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
field
|
|
1172
|
-
}));
|
|
1173
|
-
const prompt = generateMigrationPrompt(entry.label, fieldMappings);
|
|
1174
|
-
try {
|
|
1175
|
-
await navigator.clipboard.writeText(prompt);
|
|
1176
|
-
setCopied(true);
|
|
1177
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
1178
|
-
} catch {
|
|
1179
|
-
const ta = document.createElement("textarea");
|
|
1180
|
-
ta.value = prompt;
|
|
1181
|
-
document.body.appendChild(ta);
|
|
1182
|
-
ta.select();
|
|
1183
|
-
document.execCommand("copy");
|
|
1184
|
-
document.body.removeChild(ta);
|
|
1185
|
-
setCopied(true);
|
|
1186
|
-
setTimeout(() => setCopied(false), 2e3);
|
|
1531
|
+
const resolvedDims = [];
|
|
1532
|
+
for (const [col, field] of dimMappings) {
|
|
1533
|
+
if (field) resolvedDims.push({ column: col, field });
|
|
1187
1534
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1535
|
+
const migrationConfig = {
|
|
1536
|
+
componentLabel: entry.label,
|
|
1537
|
+
dimensions: resolvedDims,
|
|
1538
|
+
metrics: selectedMetrics,
|
|
1539
|
+
grain: hasTimeDim && grain ? grain : null,
|
|
1540
|
+
originalMetricColumns: analysis.metricColumns
|
|
1541
|
+
};
|
|
1542
|
+
const prompt = generateMigrationPrompt(migrationConfig);
|
|
1543
|
+
await copyToClipboard(prompt);
|
|
1544
|
+
setCopied(true);
|
|
1545
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1546
|
+
}, [entry, dimMappings, selectedMetrics, grain, hasTimeDim, analysis.metricColumns]);
|
|
1547
|
+
const canGenerate = selectedMetrics.length > 0 || Array.from(dimMappings.values()).some(Boolean);
|
|
1194
1548
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1195
|
-
/* @__PURE__ */
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
entry.currentSource
|
|
1203
|
-
] })
|
|
1204
|
-
] }),
|
|
1205
|
-
/* @__PURE__ */ jsx("button", { onClick: onClose, style: closeBtnStyle, children: "\xD7" })
|
|
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
|
+
] })
|
|
1206
1556
|
] }),
|
|
1207
1557
|
entry.sampleValues.length > 0 && /* @__PURE__ */ jsxs("div", { style: sectionStyle, children: [
|
|
1208
1558
|
/* @__PURE__ */ jsx("div", { style: sectionTitleStyle, children: "Current Data Preview" }),
|
|
1209
1559
|
/* @__PURE__ */ jsx("div", { style: { overflow: "auto" }, children: /* @__PURE__ */ jsxs("table", { style: previewTableStyle, children: [
|
|
1210
1560
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: entry.columns.map((col) => /* @__PURE__ */ jsx("th", { style: previewThStyle, children: col }, col)) }) }),
|
|
1211
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)) })
|
|
1212
|
-
] }) })
|
|
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
|
+
] })
|
|
1213
1579
|
] }),
|
|
1214
|
-
/* @__PURE__ */
|
|
1215
|
-
|
|
1216
|
-
/* @__PURE__ */
|
|
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: [
|
|
1217
1585
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
1218
1586
|
/* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Current Column" }),
|
|
1219
|
-
/* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "
|
|
1220
|
-
/* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Semantic Layer
|
|
1587
|
+
/* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Sample Values" }),
|
|
1588
|
+
/* @__PURE__ */ jsx("th", { style: mappingThStyle, children: "Semantic Layer Dimension" })
|
|
1221
1589
|
] }) }),
|
|
1222
|
-
/* @__PURE__ */ jsx("tbody", { children:
|
|
1223
|
-
|
|
1224
|
-
{
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
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
|
+
}) })
|
|
1233
1621
|
] }),
|
|
1234
|
-
|
|
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(", "),
|
|
1640
|
+
")"
|
|
1641
|
+
] })
|
|
1642
|
+
] }),
|
|
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
|
+
)) })
|
|
1235
1795
|
] }),
|
|
1236
|
-
/* @__PURE__ */
|
|
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
|
+
] })
|
|
1826
|
+
] })
|
|
1827
|
+
] }),
|
|
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)) })
|
|
1852
|
+
] }),
|
|
1853
|
+
canGenerate && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
|
|
1854
|
+
/* @__PURE__ */ jsxs(
|
|
1855
|
+
"button",
|
|
1856
|
+
{
|
|
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" },
|
|
1859
|
+
children: [
|
|
1860
|
+
previewOpen ? "\u25BC" : "\u25B6",
|
|
1861
|
+
" Prompt Preview"
|
|
1862
|
+
]
|
|
1863
|
+
}
|
|
1864
|
+
),
|
|
1865
|
+
previewOpen && /* @__PURE__ */ jsx("pre", { style: { ...codePreviewStyle, marginTop: 8, maxHeight: 200, overflow: "auto", fontSize: 10 }, children: prompt })
|
|
1866
|
+
] })
|
|
1867
|
+
] }) })
|
|
1868
|
+
] })
|
|
1869
|
+
] }),
|
|
1870
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "12px 20px", borderTop: "1px solid #e5e7eb", display: "flex", gap: 10 }, children: [
|
|
1871
|
+
/* @__PURE__ */ jsx(
|
|
1872
|
+
"button",
|
|
1873
|
+
{
|
|
1874
|
+
onClick: handleCopy,
|
|
1875
|
+
disabled: !canGenerate,
|
|
1876
|
+
style: {
|
|
1877
|
+
...secondaryBtnStyle,
|
|
1878
|
+
flex: 1,
|
|
1879
|
+
opacity: canGenerate ? 1 : 0.5,
|
|
1880
|
+
cursor: canGenerate ? "pointer" : "not-allowed"
|
|
1881
|
+
},
|
|
1882
|
+
children: copied ? "Copied!" : "Copy Prompt"
|
|
1883
|
+
}
|
|
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
|
+
)
|
|
1237
1910
|
] })
|
|
1238
1911
|
] });
|
|
1239
1912
|
}
|
|
1240
|
-
function
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
onChange
|
|
1913
|
+
function CatalogChip({
|
|
1914
|
+
field,
|
|
1915
|
+
widgets,
|
|
1916
|
+
onAddToWidget,
|
|
1917
|
+
onAddAsFilter
|
|
1246
1918
|
}) {
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
return /* @__PURE__ */ jsxs("tr", { children: [
|
|
1254
|
-
/* @__PURE__ */ jsx("td", { style: mappingTdStyle, children: /* @__PURE__ */ jsx("code", { style: { fontSize: 12, color: "#6366f1", fontFamily: "monospace" }, children: column }) }),
|
|
1255
|
-
/* @__PURE__ */ jsx("td", { style: { ...mappingTdStyle, textAlign: "center" }, children: /* @__PURE__ */ jsx(
|
|
1256
|
-
"span",
|
|
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",
|
|
1257
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}`,
|
|
1258
1935
|
style: {
|
|
1259
|
-
display: "
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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
|
|
1266
1950
|
},
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
{
|
|
1273
|
-
value: selectedField?.name ?? "",
|
|
1274
|
-
onChange: (e) => {
|
|
1275
|
-
const field = catalog.find((f) => f.name === e.target.value) ?? null;
|
|
1276
|
-
onChange(field);
|
|
1951
|
+
onMouseEnter: (e) => {
|
|
1952
|
+
e.currentTarget.style.backgroundColor = "#f3f4f6";
|
|
1953
|
+
},
|
|
1954
|
+
onMouseLeave: (e) => {
|
|
1955
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
1277
1956
|
},
|
|
1278
|
-
style: selectStyle2,
|
|
1279
1957
|
children: [
|
|
1280
|
-
/* @__PURE__ */ jsx("
|
|
1281
|
-
|
|
1282
|
-
f.displayName,
|
|
1283
|
-
" (",
|
|
1284
|
-
f.type,
|
|
1285
|
-
") \u2014 ",
|
|
1286
|
-
f.name
|
|
1287
|
-
] }, f.name))
|
|
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 })
|
|
1288
1960
|
]
|
|
1289
1961
|
}
|
|
1290
|
-
)
|
|
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
|
+
] })
|
|
2002
|
+
] })
|
|
2003
|
+
] });
|
|
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
|
+
] })
|
|
1291
2124
|
] });
|
|
1292
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
|
+
}
|
|
1293
2138
|
var backdropStyle = {
|
|
1294
2139
|
position: "fixed",
|
|
1295
2140
|
inset: 0,
|
|
@@ -1302,8 +2147,8 @@ var modalStyle = {
|
|
|
1302
2147
|
left: "50%",
|
|
1303
2148
|
transform: "translate(-50%, -50%)",
|
|
1304
2149
|
zIndex: 100001,
|
|
1305
|
-
width: "min(
|
|
1306
|
-
maxHeight: "
|
|
2150
|
+
width: "min(900px, 95vw)",
|
|
2151
|
+
maxHeight: "85vh",
|
|
1307
2152
|
backgroundColor: "#fff",
|
|
1308
2153
|
borderRadius: 12,
|
|
1309
2154
|
boxShadow: "0 8px 30px rgba(0,0,0,0.2)",
|
|
@@ -1315,8 +2160,8 @@ var modalStyle = {
|
|
|
1315
2160
|
var modalHeaderStyle = {
|
|
1316
2161
|
display: "flex",
|
|
1317
2162
|
justifyContent: "space-between",
|
|
1318
|
-
alignItems: "
|
|
1319
|
-
padding: "
|
|
2163
|
+
alignItems: "center",
|
|
2164
|
+
padding: "0 20px",
|
|
1320
2165
|
borderBottom: "1px solid #e5e7eb"
|
|
1321
2166
|
};
|
|
1322
2167
|
var closeBtnStyle = {
|
|
@@ -1329,17 +2174,15 @@ var closeBtnStyle = {
|
|
|
1329
2174
|
lineHeight: 1
|
|
1330
2175
|
};
|
|
1331
2176
|
var sectionStyle = {
|
|
1332
|
-
padding: "12px 20px"
|
|
1333
|
-
overflow: "auto",
|
|
1334
|
-
flex: 1
|
|
2177
|
+
padding: "12px 20px"
|
|
1335
2178
|
};
|
|
1336
2179
|
var sectionTitleStyle = {
|
|
1337
|
-
fontSize:
|
|
2180
|
+
fontSize: 10,
|
|
1338
2181
|
fontWeight: 700,
|
|
1339
2182
|
textTransform: "uppercase",
|
|
1340
2183
|
letterSpacing: "0.05em",
|
|
1341
2184
|
color: "#6b7280",
|
|
1342
|
-
marginBottom:
|
|
2185
|
+
marginBottom: 4
|
|
1343
2186
|
};
|
|
1344
2187
|
var previewTableStyle = {
|
|
1345
2188
|
width: "100%",
|
|
@@ -1378,7 +2221,6 @@ var mappingTdStyle = {
|
|
|
1378
2221
|
verticalAlign: "middle"
|
|
1379
2222
|
};
|
|
1380
2223
|
var selectStyle2 = {
|
|
1381
|
-
width: "100%",
|
|
1382
2224
|
padding: "4px 6px",
|
|
1383
2225
|
fontSize: 12,
|
|
1384
2226
|
border: "1px solid #d1d5db",
|
|
@@ -1386,8 +2228,28 @@ var selectStyle2 = {
|
|
|
1386
2228
|
backgroundColor: "#fff",
|
|
1387
2229
|
color: "#111827"
|
|
1388
2230
|
};
|
|
1389
|
-
var
|
|
1390
|
-
|
|
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 = {
|
|
1391
2253
|
padding: "10px 16px",
|
|
1392
2254
|
fontSize: 14,
|
|
1393
2255
|
fontWeight: 600,
|
|
@@ -1395,9 +2257,40 @@ var generateBtnStyle = {
|
|
|
1395
2257
|
backgroundColor: "#3b82f6",
|
|
1396
2258
|
border: "none",
|
|
1397
2259
|
borderRadius: 8,
|
|
1398
|
-
|
|
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
|
|
1399
2292
|
};
|
|
1400
2293
|
|
|
1401
|
-
export { AdminDashboard, DataCatalog, DataInspector, Inspectable, MetricPicker, ResultsTable, generateMigrationPrompt, matchFields };
|
|
2294
|
+
export { AdminDashboard, DataCatalog, DataInspector, Inspectable, MetricPicker, ResultsTable, analyzeChartData, canOpenInLovable, generateMigrationPrompt, generateReportPrompt, matchField, matchFields, openInLovable };
|
|
1402
2295
|
//# sourceMappingURL=components.js.map
|
|
1403
2296
|
//# sourceMappingURL=components.js.map
|