@bluemarble/bi 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -77
- package/dist/{DashboardDevtools-D0pzwSgn.js → DashboardDevtools-TFwjFdqn.js} +1437 -565
- package/dist/DashboardDevtools-TFwjFdqn.js.map +1 -0
- package/dist/{ModelDictionary-Cw4sa8tW.js → ModelDictionary-g5d84D0n.js} +67 -5
- package/dist/ModelDictionary-g5d84D0n.js.map +1 -0
- package/dist/cli.js +0 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +48 -9
- package/dist/client/index.js +186 -45
- package/dist/client/index.js.map +1 -1
- package/dist/codegen-LoQzFAle.js.map +1 -1
- package/dist/compare-DloZhg1T.js.map +1 -1
- package/dist/{formatRules-DxA9vMvz.d.ts → formatRules-C1PW2EiL.d.ts} +74 -2
- package/dist/{layout-BS3nO6OT.js → layout-BuZhfoRI.js} +167 -15
- package/dist/layout-BuZhfoRI.js.map +1 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +17 -1
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +81 -81
- package/dist/DashboardDevtools-D0pzwSgn.js.map +0 -1
- package/dist/ModelDictionary-Cw4sa8tW.js.map +0 -1
- package/dist/layout-BS3nO6OT.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c as VALUE_FORMATS, f as validateModel, g as parseColumnRef, h as modelEdges, m as measureHome, o as AGGREGATIONS, s as AGGREGATION_LABEL, t as canonicalJson, u as isAggregate, v as referencedTables } from "./codegen-LoQzFAle.js";
|
|
2
2
|
import { n as diffRows } from "./compare-DloZhg1T.js";
|
|
3
|
-
import { C as
|
|
4
|
-
import { C as parseLayoutItemKey,
|
|
3
|
+
import { C as COLUMN_VERTICAL_ALIGNS, D as RULE_OPERATOR_LABEL, E as RULE_OPERATORS, O as UNARY_OPERATORS, S as COLUMN_ALIGNS, T as NUMERIC_OPERATORS, _ as percentColumn, a as FILTER_FIELDS, b as validateLayout, d as chartReferences, g as layoutModelWarnings, i as CARD_FIELDS, k as VALUE_RULE_OPERATORS, l as chartColumns, n as CANVAS_PRESETS, o as PARAMETER_FIELDS, p as filterReferences, r as CARDS_FIELDS, s as cardsGap, t as BUILTIN_CHART_TYPES, u as chartOfType, w as DEFAULT_SCALE_MIN_COLOR, x as COLOR_SCALE_TARGETS, y as tableColumns } from "./layout-BuZhfoRI.js";
|
|
4
|
+
import { C as useDashboard, D as parseLayoutItemKey, E as layoutItemKey, T as LAYOUT_ITEM_ATTRIBUTE, g as SERIES_COLORS, n as INSPECT_ATTRIBUTE, s as formatMeasure, t as ModelDictionary, w as errorMessage } from "./ModelDictionary-g5d84D0n.js";
|
|
5
5
|
import { Alert, Autocomplete, Box, Button, Checkbox, Chip, CircularProgress, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControlLabel, IconButton, List, ListItemButton, ListSubheader, Menu, MenuItem, MenuList, Paper, Popover, Popper, Portal, Switch, Tab, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TableSortLabel, Tabs, TextField, ThemeProvider, ToggleButton, ToggleButtonGroup, Tooltip, Typography, createTheme } from "@mui/material";
|
|
6
6
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1161,8 +1161,10 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
|
|
|
1161
1161
|
] });
|
|
1162
1162
|
const charts = editing.charts;
|
|
1163
1163
|
const filters = editing.filters ?? [];
|
|
1164
|
+
const parameters = editing.parameters ?? [];
|
|
1164
1165
|
const selectedFilter = selection?.kind === "filter" ? filters.find((filter) => filter.dimension === selection.key) ?? null : null;
|
|
1165
|
-
const
|
|
1166
|
+
const selectedParameter = selection?.kind === "parameter" ? parameters.find((parameter) => parameter.name === selection.key) ?? null : null;
|
|
1167
|
+
const selected = selection?.kind === "filter" || selection?.kind === "parameter" || selection?.kind === "canvas" || selection?.kind === "cards" ? null : charts.find((chart) => chart.id === selection?.key) ?? charts[0] ?? null;
|
|
1166
1168
|
const layoutKey = entry.registered.id;
|
|
1167
1169
|
const differsFromFile = canonicalJson(editing) !== canonicalJson(entry.registered);
|
|
1168
1170
|
const dirty = canonicalJson(editing) !== referenceKey;
|
|
@@ -1183,6 +1185,8 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
|
|
|
1183
1185
|
filters: next
|
|
1184
1186
|
});
|
|
1185
1187
|
const patchFilter = (dimension, change) => setFilters(filters.map((filter) => filter.dimension === dimension ? change(filter) : filter));
|
|
1188
|
+
const setParameters = (next) => update(withField(editing, "parameters", next.length ? next : void 0));
|
|
1189
|
+
const patchParameter = (name, change) => setParameters(parameters.map((parameter) => parameter.name === name ? change(parameter) : parameter));
|
|
1186
1190
|
const toggleSidebar = () => {
|
|
1187
1191
|
const next = !sidebar;
|
|
1188
1192
|
setSidebar(next);
|
|
@@ -1210,8 +1214,11 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
|
|
|
1210
1214
|
},
|
|
1211
1215
|
onDrop: () => {
|
|
1212
1216
|
if (drag?.kind !== kind) return;
|
|
1213
|
-
if (drag.to !== drag.from && drag.to !== drag.from + 1)
|
|
1214
|
-
|
|
1217
|
+
if (drag.to !== drag.from && drag.to !== drag.from + 1) {
|
|
1218
|
+
if (kind === "chart") setCharts(moveTo(charts, drag.from, drag.to));
|
|
1219
|
+
else if (kind === "parameter") setParameters(moveTo(parameters, drag.from, drag.to));
|
|
1220
|
+
else setFilters(moveTo(filters, drag.from, drag.to));
|
|
1221
|
+
}
|
|
1215
1222
|
setDrag(null);
|
|
1216
1223
|
},
|
|
1217
1224
|
onDragEnd: () => setDrag(null)
|
|
@@ -1226,6 +1233,21 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
|
|
|
1226
1233
|
key: dimension
|
|
1227
1234
|
});
|
|
1228
1235
|
};
|
|
1236
|
+
const addParameter = () => {
|
|
1237
|
+
const measure = Object.keys(model.measures)[0];
|
|
1238
|
+
if (!measure) return;
|
|
1239
|
+
const used = new Set(parameters.map((parameter) => parameter.name));
|
|
1240
|
+
let name = "medida";
|
|
1241
|
+
for (let i = 2; used.has(name); i++) name = `medida-${i}`;
|
|
1242
|
+
setParameters([...parameters, {
|
|
1243
|
+
name,
|
|
1244
|
+
measures: [measure]
|
|
1245
|
+
}]);
|
|
1246
|
+
setSelection({
|
|
1247
|
+
kind: "parameter",
|
|
1248
|
+
key: name
|
|
1249
|
+
});
|
|
1250
|
+
};
|
|
1229
1251
|
const focus = (item) => {
|
|
1230
1252
|
setSelection(item);
|
|
1231
1253
|
if (stacked) setSidebar(false);
|
|
@@ -1236,7 +1258,7 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
|
|
|
1236
1258
|
};
|
|
1237
1259
|
const stacked = rootWidth < STACK_WIDTH;
|
|
1238
1260
|
const formColumns = editorWidth < 420 ? 1 : editorWidth < 720 ? 2 : 4;
|
|
1239
|
-
const selectedTitle = selection?.kind === "canvas" ? "Tela" : selection?.kind === "cards" ? "Cards" : selectedFilter ? selectedFilter.label || model.dimensions[selectedFilter.dimension]?.label || selectedFilter.dimension : selected ? selected.title || selected.id : "";
|
|
1261
|
+
const selectedTitle = selection?.kind === "canvas" ? "Tela" : selection?.kind === "cards" ? "Cards" : selectedFilter ? selectedFilter.label || model.dimensions[selectedFilter.dimension]?.label || selectedFilter.dimension : selectedParameter ? selectedParameter.label || selectedParameter.name : selected ? selected.title || selected.id : "";
|
|
1240
1262
|
const hover = (item) => onHover(item && itemQueryId(layoutKey, item));
|
|
1241
1263
|
const add = (type, base) => {
|
|
1242
1264
|
setAddMenu(null);
|
|
@@ -1402,6 +1424,38 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
|
|
|
1402
1424
|
}, filter.dimension);
|
|
1403
1425
|
})]
|
|
1404
1426
|
}),
|
|
1427
|
+
/* @__PURE__ */ jsxs(Section, {
|
|
1428
|
+
title: `Parâmetros (${parameters.length})`,
|
|
1429
|
+
collapseKey: "layout:lista:parametros",
|
|
1430
|
+
actions: /* @__PURE__ */ jsx(Button, {
|
|
1431
|
+
size: "small",
|
|
1432
|
+
onClick: addParameter,
|
|
1433
|
+
disabled: !Object.keys(model.measures).length,
|
|
1434
|
+
children: "+ Novo"
|
|
1435
|
+
}),
|
|
1436
|
+
children: [!parameters.length && /* @__PURE__ */ jsx(Typography, {
|
|
1437
|
+
sx: {
|
|
1438
|
+
fontSize: 12,
|
|
1439
|
+
color: "text.disabled",
|
|
1440
|
+
mb: 1
|
|
1441
|
+
},
|
|
1442
|
+
children: "Nenhum parâmetro de medida."
|
|
1443
|
+
}), parameters.map((parameter, index) => {
|
|
1444
|
+
const item = {
|
|
1445
|
+
kind: "parameter",
|
|
1446
|
+
key: parameter.name
|
|
1447
|
+
};
|
|
1448
|
+
return /* @__PURE__ */ jsx(ItemRow, {
|
|
1449
|
+
title: parameter.label || "Medida",
|
|
1450
|
+
subtitle: `${parameter.name} · ${parameter.measures.length} medida(s)`,
|
|
1451
|
+
selected: selectedParameter?.name === parameter.name,
|
|
1452
|
+
onSelect: () => focus(item),
|
|
1453
|
+
onHover: (on) => hover(on ? item : null),
|
|
1454
|
+
...dragProps("parameter", index, parameters.length),
|
|
1455
|
+
onRemove: () => setParameters(parameters.filter((other) => other.name !== parameter.name))
|
|
1456
|
+
}, parameter.name);
|
|
1457
|
+
})]
|
|
1458
|
+
}),
|
|
1405
1459
|
/* @__PURE__ */ jsx(Section, {
|
|
1406
1460
|
title: `Gráficos (${charts.length})`,
|
|
1407
1461
|
collapseKey: "layout:lista:graficos",
|
|
@@ -1501,6 +1555,10 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
|
|
|
1501
1555
|
filter: selectedFilter,
|
|
1502
1556
|
model,
|
|
1503
1557
|
formatters: entry.parts.filters ?? [],
|
|
1558
|
+
charts: editing.charts.map((chart) => ({
|
|
1559
|
+
name: chart.id,
|
|
1560
|
+
label: chart.title || chart.id
|
|
1561
|
+
})),
|
|
1504
1562
|
columns: formColumns,
|
|
1505
1563
|
onChange: (change) => {
|
|
1506
1564
|
patchFilter(selectedFilter.dimension, change);
|
|
@@ -1509,12 +1567,27 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
|
|
|
1509
1567
|
key: change(selectedFilter).dimension
|
|
1510
1568
|
});
|
|
1511
1569
|
}
|
|
1512
|
-
}, `filter:${selectedFilter.dimension}`) :
|
|
1570
|
+
}, `filter:${selectedFilter.dimension}`) : selectedParameter ? /* @__PURE__ */ jsx(ParameterForm, {
|
|
1571
|
+
parameter: selectedParameter,
|
|
1572
|
+
model,
|
|
1573
|
+
columns: formColumns,
|
|
1574
|
+
onChange: (change) => {
|
|
1575
|
+
patchParameter(selectedParameter.name, change);
|
|
1576
|
+
setSelection({
|
|
1577
|
+
kind: "parameter",
|
|
1578
|
+
key: change(selectedParameter).name
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
}, `parameter:${selectedParameter.name}`) : selected ? /* @__PURE__ */ jsx(ChartForm, {
|
|
1513
1582
|
chart: selected,
|
|
1514
1583
|
cards: editing.cards,
|
|
1515
1584
|
types: chartTypes,
|
|
1516
1585
|
model,
|
|
1517
1586
|
formatters: entry.parts.charts ?? [],
|
|
1587
|
+
measureParameters: parameters.map((parameter) => ({
|
|
1588
|
+
name: parameter.name,
|
|
1589
|
+
label: parameter.label || "Medida"
|
|
1590
|
+
})),
|
|
1518
1591
|
columns: formColumns,
|
|
1519
1592
|
onChange: (change) => {
|
|
1520
1593
|
patchChart(selected.id, change);
|
|
@@ -1820,7 +1893,7 @@ function SmallButton({ title, disabled, onClick, children }) {
|
|
|
1820
1893
|
}) })
|
|
1821
1894
|
});
|
|
1822
1895
|
}
|
|
1823
|
-
function ChartForm({ chart, cards, types, model, formatters, columns, onChange }) {
|
|
1896
|
+
function ChartForm({ chart, cards, types, model, formatters, measureParameters, columns, onChange }) {
|
|
1824
1897
|
const set = (key, value) => onChange((current) => withField(current, key, value));
|
|
1825
1898
|
const setMany = (changes) => onChange((current) => Object.entries(changes).reduce((chart, [key, value]) => withField(chart, key, value), current));
|
|
1826
1899
|
const setCard = (key, value) => onChange((current) => withField(current, "card", nonEmpty(withField(current.card ?? {}, key, value))));
|
|
@@ -1831,6 +1904,7 @@ function ChartForm({ chart, cards, types, model, formatters, columns, onChange }
|
|
|
1831
1904
|
onSetMany: setMany,
|
|
1832
1905
|
model,
|
|
1833
1906
|
formatters,
|
|
1907
|
+
measureParameters,
|
|
1834
1908
|
columns: chartColumns(chart, types),
|
|
1835
1909
|
formColumns: columns,
|
|
1836
1910
|
chosenDimensions: chartReferences(chart, types).dimensions,
|
|
@@ -1866,23 +1940,42 @@ function ChartForm({ chart, cards, types, model, formatters, columns, onChange }
|
|
|
1866
1940
|
formatters: [],
|
|
1867
1941
|
columns: [],
|
|
1868
1942
|
formColumns: columns,
|
|
1869
|
-
chosenDimensions: []
|
|
1943
|
+
chosenDimensions: [],
|
|
1944
|
+
sectionActions: { Card: /* @__PURE__ */ jsx(Button, {
|
|
1945
|
+
size: "small",
|
|
1946
|
+
disabled: !chart.card || !Object.keys(chart.card).length,
|
|
1947
|
+
onClick: () => onChange((current) => withField(current, "card", void 0)),
|
|
1948
|
+
children: "Usar valor padrão"
|
|
1949
|
+
}) }
|
|
1870
1950
|
})] });
|
|
1871
1951
|
}
|
|
1872
|
-
function FilterForm({ filter, model, formatters, columns, onChange }) {
|
|
1952
|
+
function FilterForm({ filter, model, formatters, charts, columns, onChange }) {
|
|
1873
1953
|
return /* @__PURE__ */ jsx(FieldsForm, {
|
|
1874
1954
|
fields: FILTER_FIELDS,
|
|
1875
1955
|
values: filter,
|
|
1876
1956
|
onSet: (key, value) => onChange((current) => withField(current, key, value)),
|
|
1877
1957
|
model,
|
|
1878
1958
|
formatters,
|
|
1959
|
+
charts,
|
|
1879
1960
|
columns: [],
|
|
1880
1961
|
formColumns: columns,
|
|
1881
1962
|
chosenDimensions: [filter.dimension]
|
|
1882
1963
|
});
|
|
1883
1964
|
}
|
|
1965
|
+
function ParameterForm({ parameter, model, columns, onChange }) {
|
|
1966
|
+
return /* @__PURE__ */ jsx(FieldsForm, {
|
|
1967
|
+
fields: PARAMETER_FIELDS,
|
|
1968
|
+
values: parameter,
|
|
1969
|
+
onSet: (key, value) => onChange((current) => withField(current, key, value)),
|
|
1970
|
+
model,
|
|
1971
|
+
formatters: [],
|
|
1972
|
+
columns: parameter.measures,
|
|
1973
|
+
formColumns: columns,
|
|
1974
|
+
chosenDimensions: []
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1884
1977
|
/** Formulário gerado dos `FieldSpec` (campos do tipo de gráfico ou do filtro). */
|
|
1885
|
-
function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters, columns, formColumns, chosenDimensions, chosenMeasures = [], header }) {
|
|
1978
|
+
function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters, columns, formColumns, chosenDimensions, chosenMeasures = [], charts = [], measureParameters = [], sectionActions, header }) {
|
|
1886
1979
|
const dimensions = Object.entries(model.dimensions).map(([name, dim]) => ({
|
|
1887
1980
|
name,
|
|
1888
1981
|
label: dim.label
|
|
@@ -2077,6 +2170,9 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
|
|
|
2077
2170
|
case "dimensions": return dimensions;
|
|
2078
2171
|
case "measure":
|
|
2079
2172
|
case "measures": return measures;
|
|
2173
|
+
case "filters": return dimensions;
|
|
2174
|
+
case "charts": return charts;
|
|
2175
|
+
case "parameter": return measureParameters;
|
|
2080
2176
|
case "formatter": return formatters.map((name) => ({ name }));
|
|
2081
2177
|
case "column": return spec.outside ? fieldOptions : columnOptionsOf(columns);
|
|
2082
2178
|
case "enum": return spec.values.map((name) => ({
|
|
@@ -2165,6 +2261,28 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
|
|
|
2165
2261
|
}
|
|
2166
2262
|
});
|
|
2167
2263
|
}
|
|
2264
|
+
case "filters": return /* @__PURE__ */ jsx(ColumnList, {
|
|
2265
|
+
items: (value ?? []).map((name) => ({
|
|
2266
|
+
kind: "dimension",
|
|
2267
|
+
name
|
|
2268
|
+
})),
|
|
2269
|
+
options: { dimension: options(spec) },
|
|
2270
|
+
onChange: (next) => {
|
|
2271
|
+
const list = next.map((item) => item.name);
|
|
2272
|
+
set(key, list.length ? list : void 0);
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
case "charts": return /* @__PURE__ */ jsx(ColumnList, {
|
|
2276
|
+
items: (value ?? []).map((name) => ({
|
|
2277
|
+
kind: "chart",
|
|
2278
|
+
name
|
|
2279
|
+
})),
|
|
2280
|
+
options: { chart: options(spec) },
|
|
2281
|
+
onChange: (next) => {
|
|
2282
|
+
const list = next.map((item) => item.name);
|
|
2283
|
+
set(key, list.length ? list : void 0);
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2168
2286
|
case "valueRule": return /* @__PURE__ */ jsx(ValueRuleInput, {
|
|
2169
2287
|
rule: value,
|
|
2170
2288
|
label: spec.label,
|
|
@@ -2259,13 +2377,14 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
|
|
|
2259
2377
|
...pairedFormatters
|
|
2260
2378
|
].filter(Boolean));
|
|
2261
2379
|
const sections = [...new Set(entries.filter(([key]) => !hidden.has(key)).map(([, spec]) => spec.section))];
|
|
2262
|
-
const fullRow = (spec) => spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "formatters" || spec.kind === "formatRules" || spec.kind === "colorScales" || spec.kind === "colors" || spec.kind === "valueRule";
|
|
2380
|
+
const fullRow = (spec) => spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "filters" || spec.kind === "charts" || spec.kind === "formatters" || spec.kind === "formatRules" || spec.kind === "colorScales" || spec.kind === "colors" || spec.kind === "valueRule";
|
|
2263
2381
|
const gridColumn = (spec, key) => fullRow(spec) ? "1 / -1" : key === "title" && formColumns > 1 ? "span 2" : void 0;
|
|
2264
2382
|
return /* @__PURE__ */ jsx(Fragment, { children: sections.map((section) => {
|
|
2265
2383
|
const inSection = entries.filter(([, spec]) => spec.section === section);
|
|
2266
2384
|
return /* @__PURE__ */ jsx(Section, {
|
|
2267
2385
|
title: section,
|
|
2268
2386
|
collapseKey: `layout:form:${section}`,
|
|
2387
|
+
actions: sectionActions?.[section],
|
|
2269
2388
|
children: /* @__PURE__ */ jsxs(Box, {
|
|
2270
2389
|
sx: formGrid(formColumns),
|
|
2271
2390
|
children: [section === sections[0] && header, inSection.filter(([key]) => !hidden.has(key)).map(([key, spec]) => /* @__PURE__ */ jsx(Box, {
|
|
@@ -3237,7 +3356,7 @@ function ColumnOptions({ kind, name, model, label, onLabel, format, onFormat, fo
|
|
|
3237
3356
|
value: formatter,
|
|
3238
3357
|
options: formatters.map((option) => ({ name: option })),
|
|
3239
3358
|
onChange: onFormatter
|
|
3240
|
-
}), "
|
|
3359
|
+
}), "Built-in (to_month_name, to_date, ...) e os passados ao <DashboardCharts formatters={{ ... }}> da página"),
|
|
3241
3360
|
format !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3242
3361
|
/* @__PURE__ */ jsxs(Box, {
|
|
3243
3362
|
sx: {
|
|
@@ -3436,6 +3555,12 @@ const KIND_STYLE = {
|
|
|
3436
3555
|
color: "#9775fa",
|
|
3437
3556
|
noun: "% do total",
|
|
3438
3557
|
group: "% do total (da medida)"
|
|
3558
|
+
},
|
|
3559
|
+
chart: {
|
|
3560
|
+
icon: "▦",
|
|
3561
|
+
color: "#51cf66",
|
|
3562
|
+
noun: "gráfico",
|
|
3563
|
+
group: "Gráficos"
|
|
3439
3564
|
}
|
|
3440
3565
|
};
|
|
3441
3566
|
/**
|
|
@@ -3821,186 +3946,689 @@ function freeName(prefix, taken) {
|
|
|
3821
3946
|
for (let index = 2;; index++) if (!taken.includes(`${prefix}${index}`)) return `${prefix}${index}`;
|
|
3822
3947
|
}
|
|
3823
3948
|
//#endregion
|
|
3824
|
-
//#region src/devtools/
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
*
|
|
3830
|
-
* O destaque é um `<pre>` colorido por baixo do `<textarea>` de texto transparente: mesma fonte,
|
|
3831
|
-
* mesmo recuo e mesma quebra de linha, com a rolagem sincronizada. O cursor, a seleção e o
|
|
3832
|
-
* autocomplete continuam sendo do textarea.
|
|
3833
|
-
*/
|
|
3834
|
-
const PAD = {
|
|
3835
|
-
top: 8.5,
|
|
3836
|
-
right: 30,
|
|
3837
|
-
bottom: 8.5,
|
|
3838
|
-
left: 12
|
|
3839
|
-
};
|
|
3840
|
-
const LINE_HEIGHT = "18px";
|
|
3841
|
-
const SqlCompletionsContext = createContext({
|
|
3842
|
-
tables: [],
|
|
3843
|
-
modelColumns: {}
|
|
3844
|
-
});
|
|
3845
|
-
const KEYWORDS = new Set("WHERE ON LEFT RIGHT INNER OUTER JOIN GROUP ORDER LIMIT USING AND OR AS SET HAVING UNION".split(" "));
|
|
3846
|
-
/** `alias -> tabela` a partir dos `FROM x a` / `JOIN x AS a` do texto. */
|
|
3847
|
-
function aliases(sql) {
|
|
3848
|
-
const map = /* @__PURE__ */ new Map();
|
|
3849
|
-
for (const match of sql.matchAll(/\b(?:FROM|JOIN)\s+`?(\w+)`?(?:\s+(?:AS\s+)?`?(\w+)`?)?/gi)) {
|
|
3850
|
-
const [, table, alias] = match;
|
|
3851
|
-
map.set(table.toLowerCase(), table);
|
|
3852
|
-
if (alias && !KEYWORDS.has(alias.toUpperCase())) map.set(alias.toLowerCase(), table);
|
|
3853
|
-
}
|
|
3854
|
-
return map;
|
|
3949
|
+
//#region src/devtools/QueryBuilder.tsx
|
|
3950
|
+
const RESERVED = new Set("select from where group order by limit key keys index table desc asc and or not in is null like as on join case when then else end interval range rows".split(" "));
|
|
3951
|
+
const quote = (name) => /^[A-Za-z_]\w*$/.test(name) && !RESERVED.has(name.toLowerCase()) ? name : `\`${name}\``;
|
|
3952
|
+
const unquote = (name) => name.trim().replace(/^`(.*)`$/, "$1");
|
|
3953
|
+
function buildSelect(table, columns) {
|
|
3954
|
+
return `SELECT\n${columns.length ? columns.map((column) => ` ${quote(column)}`).join(",\n") : " *"}\nFROM ${quote(table)}`;
|
|
3855
3955
|
}
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
const
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
if (qualified) {
|
|
3867
|
-
const [, qualifier, typed] = qualified;
|
|
3868
|
-
prefix = typed;
|
|
3869
|
-
if (completions.modelColumns[qualifier]) candidates = completions.modelColumns[qualifier].map((text) => ({
|
|
3870
|
-
text,
|
|
3871
|
-
detail: qualifier
|
|
3872
|
-
}));
|
|
3873
|
-
else candidates = columnsOf(aliases(sql).get(qualifier.toLowerCase()) ?? qualifier);
|
|
3874
|
-
} else {
|
|
3875
|
-
const word = /[A-Za-z_]\w*$/.exec(before);
|
|
3876
|
-
if (!word || word[0].length < 2) return null;
|
|
3877
|
-
prefix = word[0];
|
|
3878
|
-
const referenced = [...new Set(aliases(sql).values())];
|
|
3879
|
-
candidates = [
|
|
3880
|
-
...Object.keys(completions.modelColumns).map((text) => ({
|
|
3881
|
-
text,
|
|
3882
|
-
detail: "tabela do modelo"
|
|
3883
|
-
})),
|
|
3884
|
-
...completions.tables.map((table) => ({
|
|
3885
|
-
text: table.name,
|
|
3886
|
-
detail: table.type
|
|
3887
|
-
})),
|
|
3888
|
-
...referenced.flatMap(columnsOf)
|
|
3889
|
-
];
|
|
3890
|
-
}
|
|
3891
|
-
const lower = prefix.toLowerCase();
|
|
3892
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3893
|
-
const items = candidates.filter((item) => {
|
|
3894
|
-
const text = item.text.toLowerCase();
|
|
3895
|
-
if (seen.has(text) || text === lower || !text.includes(lower)) return false;
|
|
3896
|
-
seen.add(text);
|
|
3897
|
-
return true;
|
|
3898
|
-
}).sort((a, b) => Number(!a.text.toLowerCase().startsWith(lower)) - Number(!b.text.toLowerCase().startsWith(lower)) || a.text.localeCompare(b.text)).slice(0, 8);
|
|
3899
|
-
return items.length ? {
|
|
3900
|
-
from: caret - prefix.length,
|
|
3901
|
-
items
|
|
3902
|
-
} : null;
|
|
3956
|
+
/** Tabela e colunas de um `SELECT a, b FROM t` (sem WHERE, JOIN etc.); senão `null`. */
|
|
3957
|
+
function parseSelect(sql) {
|
|
3958
|
+
const match = /^\s*SELECT\s+([\s\S]+?)\s+FROM\s+(`[^`]+`|\w+)\s*;?\s*$/i.exec(sql);
|
|
3959
|
+
if (!match) return null;
|
|
3960
|
+
const columns = match[1].trim() === "*" ? [] : match[1].split(",").map(unquote);
|
|
3961
|
+
if (columns.some((column) => !/^[^\s`().]+$/.test(column))) return null;
|
|
3962
|
+
return {
|
|
3963
|
+
table: unquote(match[2]),
|
|
3964
|
+
columns
|
|
3965
|
+
};
|
|
3903
3966
|
}
|
|
3904
|
-
function
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3967
|
+
function QueryBuilder({ open, onClose, tables, sql, onApply }) {
|
|
3968
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
3969
|
+
open,
|
|
3970
|
+
onClose,
|
|
3971
|
+
maxWidth: "md",
|
|
3972
|
+
fullWidth: true,
|
|
3973
|
+
children: open && /* @__PURE__ */ jsx(Builder, {
|
|
3974
|
+
tables,
|
|
3975
|
+
sql,
|
|
3976
|
+
onClose,
|
|
3977
|
+
onApply
|
|
3978
|
+
})
|
|
3979
|
+
});
|
|
3980
|
+
}
|
|
3981
|
+
function Builder({ tables, sql, onClose, onApply }) {
|
|
3982
|
+
const [initial] = useState(() => {
|
|
3983
|
+
const parsed = parseSelect(sql);
|
|
3984
|
+
const table = parsed && tables.find((item) => item.name === parsed.table);
|
|
3985
|
+
if (!parsed || !table) return {
|
|
3986
|
+
table: null,
|
|
3987
|
+
columns: []
|
|
3988
|
+
};
|
|
3989
|
+
const known = new Set(table.columns.map((column) => column.name));
|
|
3990
|
+
return {
|
|
3991
|
+
table,
|
|
3992
|
+
columns: parsed.columns.filter((column) => known.has(column))
|
|
3993
|
+
};
|
|
3994
|
+
});
|
|
3995
|
+
const [table, setTable] = useState(initial.table);
|
|
3996
|
+
const [selected, setSelected] = useState(initial.columns);
|
|
3997
|
+
const [search, setSearch] = useState("");
|
|
3998
|
+
const visible = useMemo(() => {
|
|
3999
|
+
const lower = search.trim().toLowerCase();
|
|
4000
|
+
return (table?.columns ?? []).filter((column) => column.name.toLowerCase().includes(lower));
|
|
4001
|
+
}, [table, search]);
|
|
4002
|
+
const ordered = (table?.columns ?? []).map((column) => column.name).filter((name) => selected.includes(name));
|
|
4003
|
+
const result = table ? buildSelect(table.name, ordered) : "";
|
|
4004
|
+
const allVisible = visible.length > 0 && visible.every((column) => selected.includes(column.name));
|
|
4005
|
+
const toggle = (name) => setSelected((current) => current.includes(name) ? current.filter((item) => item !== name) : [...current, name]);
|
|
4006
|
+
const toggleVisible = () => {
|
|
4007
|
+
const names = visible.map((column) => column.name);
|
|
4008
|
+
setSelected((current) => allVisible ? current.filter((name) => !names.includes(name)) : [.../* @__PURE__ */ new Set([...current, ...names])]);
|
|
3922
4009
|
};
|
|
3923
4010
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3924
|
-
/* @__PURE__ */ jsx(
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
value,
|
|
3934
|
-
placeholder,
|
|
3935
|
-
error: !value.trim() || !!error,
|
|
3936
|
-
helperText: error ?? warning,
|
|
3937
|
-
onChange: (event) => {
|
|
3938
|
-
onChange(event.target.value);
|
|
3939
|
-
const found = suggest(event.target.value, event.target.selectionStart ?? 0, completions);
|
|
3940
|
-
setState(found && {
|
|
3941
|
-
...found,
|
|
3942
|
-
index: 0
|
|
3943
|
-
});
|
|
3944
|
-
},
|
|
3945
|
-
onKeyDown: (event) => {
|
|
3946
|
-
if (!state) return;
|
|
3947
|
-
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
3948
|
-
event.preventDefault();
|
|
3949
|
-
const step = event.key === "ArrowDown" ? 1 : -1;
|
|
3950
|
-
setState({
|
|
3951
|
-
...state,
|
|
3952
|
-
index: (state.index + step + state.items.length) % state.items.length
|
|
3953
|
-
});
|
|
3954
|
-
} else if (event.key === "Enter" || event.key === "Tab") {
|
|
3955
|
-
event.preventDefault();
|
|
3956
|
-
accept(state.items[state.index]);
|
|
3957
|
-
} else if (event.key === "Escape") {
|
|
3958
|
-
event.stopPropagation();
|
|
3959
|
-
setState(null);
|
|
3960
|
-
}
|
|
4011
|
+
/* @__PURE__ */ jsx(DialogTitle, {
|
|
4012
|
+
sx: { fontSize: 14 },
|
|
4013
|
+
children: "Criar query"
|
|
4014
|
+
}),
|
|
4015
|
+
/* @__PURE__ */ jsxs(DialogContent, {
|
|
4016
|
+
sx: {
|
|
4017
|
+
display: "flex",
|
|
4018
|
+
flexDirection: "column",
|
|
4019
|
+
gap: 1.5
|
|
3961
4020
|
},
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
component: "pre",
|
|
3972
|
-
ref: highlight,
|
|
3973
|
-
"aria-hidden": true,
|
|
3974
|
-
sx: {
|
|
3975
|
-
...textSx,
|
|
3976
|
-
m: 0,
|
|
3977
|
-
p: 0,
|
|
3978
|
-
position: "absolute",
|
|
3979
|
-
top: PAD.top,
|
|
3980
|
-
right: PAD.right,
|
|
3981
|
-
bottom: PAD.bottom,
|
|
3982
|
-
left: PAD.left,
|
|
3983
|
-
overflow: "hidden",
|
|
3984
|
-
pointerEvents: "none",
|
|
3985
|
-
color: "text.primary"
|
|
4021
|
+
children: [
|
|
4022
|
+
/* @__PURE__ */ jsx(Autocomplete, {
|
|
4023
|
+
size: "small",
|
|
4024
|
+
options: tables,
|
|
4025
|
+
value: table,
|
|
4026
|
+
onChange: (_, value) => {
|
|
4027
|
+
setTable(value);
|
|
4028
|
+
setSelected([]);
|
|
4029
|
+
setSearch("");
|
|
3986
4030
|
},
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
4031
|
+
getOptionLabel: (option) => option.name,
|
|
4032
|
+
isOptionEqualToValue: (option, value) => option.name === value.name,
|
|
4033
|
+
noOptionsText: tables.length ? "Nenhuma tabela" : "Carregando as tabelas do banco…",
|
|
4034
|
+
renderOption: ({ key, ...props }, option) => /* @__PURE__ */ jsxs(Box, {
|
|
4035
|
+
component: "li",
|
|
4036
|
+
...props,
|
|
4037
|
+
sx: {
|
|
4038
|
+
gap: 2,
|
|
4039
|
+
fontFamily: MONO,
|
|
4040
|
+
fontSize: 12
|
|
4041
|
+
},
|
|
4042
|
+
children: [option.name, /* @__PURE__ */ jsxs(Box, {
|
|
4043
|
+
component: "span",
|
|
4044
|
+
sx: {
|
|
4045
|
+
ml: "auto",
|
|
4046
|
+
color: "text.secondary",
|
|
4047
|
+
fontSize: 11
|
|
4048
|
+
},
|
|
4049
|
+
children: [
|
|
4050
|
+
option.type,
|
|
4051
|
+
" · ",
|
|
4052
|
+
option.columns.length,
|
|
4053
|
+
" colunas"
|
|
4054
|
+
]
|
|
4055
|
+
})]
|
|
4056
|
+
}, key),
|
|
4057
|
+
renderInput: (params) => /* @__PURE__ */ jsx(TextField, {
|
|
4058
|
+
...params,
|
|
4059
|
+
label: "tabela",
|
|
4060
|
+
sx: { mt: 1 }
|
|
4061
|
+
})
|
|
4062
|
+
}),
|
|
4063
|
+
table && /* @__PURE__ */ jsxs(Box, {
|
|
3994
4064
|
sx: {
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4065
|
+
display: "flex",
|
|
4066
|
+
flexDirection: "column",
|
|
4067
|
+
gap: .5
|
|
4068
|
+
},
|
|
4069
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
4070
|
+
sx: {
|
|
4071
|
+
display: "flex",
|
|
4072
|
+
alignItems: "center",
|
|
4073
|
+
gap: 1
|
|
4074
|
+
},
|
|
4075
|
+
children: [
|
|
4076
|
+
/* @__PURE__ */ jsx(FormControlLabel, {
|
|
4077
|
+
control: /* @__PURE__ */ jsx(Checkbox, {
|
|
4078
|
+
size: "small",
|
|
4079
|
+
checked: allVisible,
|
|
4080
|
+
indeterminate: !allVisible && visible.some((c) => selected.includes(c.name)),
|
|
4081
|
+
onChange: toggleVisible
|
|
4082
|
+
}),
|
|
4083
|
+
label: /* @__PURE__ */ jsx(Typography, {
|
|
4084
|
+
sx: { fontSize: 12 },
|
|
4085
|
+
children: "Todas"
|
|
4086
|
+
})
|
|
4087
|
+
}),
|
|
4088
|
+
/* @__PURE__ */ jsxs(Typography, {
|
|
4089
|
+
sx: {
|
|
4090
|
+
fontSize: 12,
|
|
4091
|
+
color: "text.secondary"
|
|
4092
|
+
},
|
|
4093
|
+
children: [
|
|
4094
|
+
selected.length,
|
|
4095
|
+
" de ",
|
|
4096
|
+
table.columns.length,
|
|
4097
|
+
" marcadas",
|
|
4098
|
+
!selected.length && " (nenhuma = *)"
|
|
4099
|
+
]
|
|
4100
|
+
}),
|
|
4101
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
4102
|
+
size: "small",
|
|
4103
|
+
placeholder: "filtrar colunas",
|
|
4104
|
+
value: search,
|
|
4105
|
+
onChange: (event) => setSearch(event.target.value),
|
|
4106
|
+
sx: {
|
|
4107
|
+
ml: "auto",
|
|
4108
|
+
width: 200
|
|
4109
|
+
}
|
|
4110
|
+
})
|
|
4111
|
+
]
|
|
4112
|
+
}), /* @__PURE__ */ jsx(Box, {
|
|
4113
|
+
sx: {
|
|
4114
|
+
maxHeight: 280,
|
|
4115
|
+
overflow: "auto",
|
|
4116
|
+
border: 1,
|
|
4117
|
+
borderColor: "divider",
|
|
4118
|
+
borderRadius: 1,
|
|
4119
|
+
px: 1,
|
|
4120
|
+
display: "grid",
|
|
4121
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))"
|
|
4122
|
+
},
|
|
4123
|
+
children: visible.map((column) => /* @__PURE__ */ jsx(FormControlLabel, {
|
|
4124
|
+
control: /* @__PURE__ */ jsx(Checkbox, {
|
|
4125
|
+
size: "small",
|
|
4126
|
+
checked: selected.includes(column.name),
|
|
4127
|
+
onChange: () => toggle(column.name)
|
|
4128
|
+
}),
|
|
4129
|
+
label: /* @__PURE__ */ jsxs(Box, {
|
|
4130
|
+
component: "span",
|
|
4131
|
+
sx: {
|
|
4132
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4133
|
+
fontSize: 12
|
|
4134
|
+
},
|
|
4135
|
+
children: [
|
|
4136
|
+
column.name,
|
|
4137
|
+
" ",
|
|
4138
|
+
/* @__PURE__ */ jsx(Box, {
|
|
4139
|
+
component: "span",
|
|
4140
|
+
sx: {
|
|
4141
|
+
color: "text.secondary",
|
|
4142
|
+
fontSize: 11
|
|
4143
|
+
},
|
|
4144
|
+
children: column.type
|
|
4145
|
+
})
|
|
4146
|
+
]
|
|
4147
|
+
}),
|
|
4148
|
+
sx: {
|
|
4149
|
+
m: 0,
|
|
4150
|
+
minWidth: 0
|
|
4151
|
+
}
|
|
4152
|
+
}, column.name))
|
|
4153
|
+
})]
|
|
4154
|
+
}),
|
|
4155
|
+
result && /* @__PURE__ */ jsx(Box, {
|
|
4156
|
+
component: "pre",
|
|
4157
|
+
sx: {
|
|
4158
|
+
m: 0,
|
|
4159
|
+
p: 1,
|
|
4160
|
+
border: 1,
|
|
4161
|
+
borderColor: "divider",
|
|
4162
|
+
borderRadius: 1,
|
|
4163
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4164
|
+
fontSize: 12,
|
|
4165
|
+
maxHeight: 200,
|
|
4166
|
+
overflow: "auto"
|
|
4167
|
+
},
|
|
4168
|
+
children: /* @__PURE__ */ jsx(HighlightedSql, { code: result })
|
|
4169
|
+
})
|
|
4170
|
+
]
|
|
4171
|
+
}),
|
|
4172
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
|
|
4173
|
+
onClick: onClose,
|
|
4174
|
+
children: "Cancelar"
|
|
4175
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
4176
|
+
variant: "contained",
|
|
4177
|
+
disabled: !table,
|
|
4178
|
+
onClick: () => {
|
|
4179
|
+
onApply(result);
|
|
4180
|
+
onClose();
|
|
4181
|
+
},
|
|
4182
|
+
title: "Substitui o SQL atual",
|
|
4183
|
+
children: "Usar no SQL"
|
|
4184
|
+
})] })
|
|
4185
|
+
] });
|
|
4186
|
+
}
|
|
4187
|
+
//#endregion
|
|
4188
|
+
//#region src/devtools/queryTemplateDefs.ts
|
|
4189
|
+
/**
|
|
4190
|
+
* `WITH` da origem: a tabela do banco entra pelo nome; o SQL de uma tabela do modelo vira o CTE
|
|
4191
|
+
* `origem`, antes dos CTEs do template.
|
|
4192
|
+
*/
|
|
4193
|
+
function withSource(source) {
|
|
4194
|
+
if (source.kind === "database") return {
|
|
4195
|
+
ctes: [],
|
|
4196
|
+
from: quote(source.table)
|
|
4197
|
+
};
|
|
4198
|
+
return {
|
|
4199
|
+
ctes: [`origem AS (\n${source.sql.trim().replace(/;+\s*$/, "").replace(/^/gm, " ")}\n)`],
|
|
4200
|
+
from: "origem"
|
|
4201
|
+
};
|
|
4202
|
+
}
|
|
4203
|
+
/**
|
|
4204
|
+
* Um dia por linha, do menor ao maior valor da coluna, com ano e mês. É a tabela "Calendário" do
|
|
4205
|
+
* Power BI: relacione a coluna de data da fato com `dia`.
|
|
4206
|
+
*
|
|
4207
|
+
* Não é um `WITH RECURSIVE` de um dia por iteração: o MySQL (`cte_max_recursion_depth`) e o MariaDB
|
|
4208
|
+
* (`max_recursive_iterations`) param em 1000 iterações, e o MariaDB corta em silêncio — o calendário
|
|
4209
|
+
* acabaria ~2,7 anos depois do início. A recursão só gera 0–999 e o dia sai de `mil * 1000 + n`.
|
|
4210
|
+
*/
|
|
4211
|
+
function calendarSql(source, column) {
|
|
4212
|
+
const { ctes, from } = withSource(source);
|
|
4213
|
+
const col = quote(column);
|
|
4214
|
+
return [
|
|
4215
|
+
`WITH RECURSIVE ${[
|
|
4216
|
+
...ctes,
|
|
4217
|
+
[
|
|
4218
|
+
"limites AS (",
|
|
4219
|
+
` SELECT DATE(MIN(${col})) AS inicio, DATE(MAX(${col})) AS fim`,
|
|
4220
|
+
` FROM ${from}`,
|
|
4221
|
+
")"
|
|
4222
|
+
],
|
|
4223
|
+
[
|
|
4224
|
+
"numeros AS (",
|
|
4225
|
+
" SELECT 0 AS n",
|
|
4226
|
+
" UNION ALL",
|
|
4227
|
+
" SELECT n + 1 FROM numeros WHERE n < 999",
|
|
4228
|
+
")"
|
|
4229
|
+
],
|
|
4230
|
+
[
|
|
4231
|
+
"dias AS (",
|
|
4232
|
+
" SELECT inicio + INTERVAL (mil.n * 1000 + numeros.n) DAY AS dia",
|
|
4233
|
+
" FROM limites, numeros mil, numeros",
|
|
4234
|
+
" WHERE mil.n <= DATEDIFF(fim, inicio) DIV 1000",
|
|
4235
|
+
" AND mil.n * 1000 + numeros.n <= DATEDIFF(fim, inicio)",
|
|
4236
|
+
")"
|
|
4237
|
+
]
|
|
4238
|
+
].map((part) => Array.isArray(part) ? part.join("\n") : part).join(",\n")}`,
|
|
4239
|
+
"SELECT",
|
|
4240
|
+
" dia,",
|
|
4241
|
+
" YEAR(dia) AS ano,",
|
|
4242
|
+
" MONTH(dia) AS mes",
|
|
4243
|
+
"FROM dias"
|
|
4244
|
+
].join("\n");
|
|
4245
|
+
}
|
|
4246
|
+
const QUERY_TEMPLATES = [{
|
|
4247
|
+
id: "calendar",
|
|
4248
|
+
label: "Calendário",
|
|
4249
|
+
description: "Um dia por linha (com ano e mês) entre a menor e a maior data da coluna. Relacione a data da fato com `dia`.",
|
|
4250
|
+
columnLabel: "coluna de data",
|
|
4251
|
+
preferredTypes: /date|time/i,
|
|
4252
|
+
build: calendarSql
|
|
4253
|
+
}];
|
|
4254
|
+
//#endregion
|
|
4255
|
+
//#region src/devtools/QueryTemplates.tsx
|
|
4256
|
+
function QueryTemplates({ open, onClose, ...props }) {
|
|
4257
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
4258
|
+
open,
|
|
4259
|
+
onClose,
|
|
4260
|
+
maxWidth: "md",
|
|
4261
|
+
fullWidth: true,
|
|
4262
|
+
children: open && /* @__PURE__ */ jsx(Templates, {
|
|
4263
|
+
onClose,
|
|
4264
|
+
...props
|
|
4265
|
+
})
|
|
4266
|
+
});
|
|
4267
|
+
}
|
|
4268
|
+
function Templates({ tables, modelTables, onClose, onApply }) {
|
|
4269
|
+
const [templateId, setTemplateId] = useState(QUERY_TEMPLATES[0].id);
|
|
4270
|
+
const [source, setSource] = useState(null);
|
|
4271
|
+
const [column, setColumn] = useState(null);
|
|
4272
|
+
const template = QUERY_TEMPLATES.find((item) => item.id === templateId) ?? QUERY_TEMPLATES[0];
|
|
4273
|
+
const sources = useMemo(() => [...Object.entries(modelTables).map(([name, table]) => ({
|
|
4274
|
+
key: `model:${name}`,
|
|
4275
|
+
name,
|
|
4276
|
+
group: "Tabelas do modelo",
|
|
4277
|
+
columns: table.columns.map((column) => ({ name: column })),
|
|
4278
|
+
source: {
|
|
4279
|
+
kind: "model",
|
|
4280
|
+
sql: table.sql
|
|
4281
|
+
}
|
|
4282
|
+
})), ...tables.map((table) => ({
|
|
4283
|
+
key: `database:${table.name}`,
|
|
4284
|
+
name: table.name,
|
|
4285
|
+
group: "Tabelas do banco",
|
|
4286
|
+
columns: table.columns,
|
|
4287
|
+
source: {
|
|
4288
|
+
kind: "database",
|
|
4289
|
+
table: table.name
|
|
4290
|
+
}
|
|
4291
|
+
}))], [tables, modelTables]);
|
|
4292
|
+
const columns = useMemo(() => {
|
|
4293
|
+
const list = source?.columns ?? [];
|
|
4294
|
+
const preferred = (type) => !!type && !!template.preferredTypes?.test(type);
|
|
4295
|
+
return [...list.filter((c) => preferred(c.type)), ...list.filter((c) => !preferred(c.type))];
|
|
4296
|
+
}, [source, template]);
|
|
4297
|
+
const result = source && column ? template.build(source.source, column) : "";
|
|
4298
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4299
|
+
/* @__PURE__ */ jsx(DialogTitle, {
|
|
4300
|
+
sx: { fontSize: 14 },
|
|
4301
|
+
children: "Importar template"
|
|
4302
|
+
}),
|
|
4303
|
+
/* @__PURE__ */ jsxs(DialogContent, {
|
|
4304
|
+
sx: {
|
|
4305
|
+
display: "flex",
|
|
4306
|
+
flexDirection: "column",
|
|
4307
|
+
gap: 1.5
|
|
4308
|
+
},
|
|
4309
|
+
children: [
|
|
4310
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
4311
|
+
select: true,
|
|
4312
|
+
size: "small",
|
|
4313
|
+
label: "template",
|
|
4314
|
+
value: template.id,
|
|
4315
|
+
onChange: (event) => setTemplateId(event.target.value),
|
|
4316
|
+
helperText: template.description,
|
|
4317
|
+
sx: { mt: 1 },
|
|
4318
|
+
children: QUERY_TEMPLATES.map((item) => /* @__PURE__ */ jsx(MenuItem, {
|
|
4319
|
+
value: item.id,
|
|
4320
|
+
children: item.label
|
|
4321
|
+
}, item.id))
|
|
4322
|
+
}),
|
|
4323
|
+
/* @__PURE__ */ jsx(Autocomplete, {
|
|
4324
|
+
size: "small",
|
|
4325
|
+
options: sources,
|
|
4326
|
+
value: source,
|
|
4327
|
+
onChange: (_, value) => {
|
|
4328
|
+
setSource(value);
|
|
4329
|
+
setColumn(null);
|
|
4330
|
+
},
|
|
4331
|
+
groupBy: (option) => option.group,
|
|
4332
|
+
getOptionLabel: (option) => option.name,
|
|
4333
|
+
isOptionEqualToValue: (option, value) => option.key === value.key,
|
|
4334
|
+
noOptionsText: "Nenhuma tabela",
|
|
4335
|
+
renderOption: ({ key, ...props }, option) => /* @__PURE__ */ jsxs(Box, {
|
|
4336
|
+
component: "li",
|
|
4337
|
+
...props,
|
|
4338
|
+
sx: {
|
|
4339
|
+
gap: 2,
|
|
4340
|
+
fontFamily: MONO,
|
|
4341
|
+
fontSize: 12
|
|
4342
|
+
},
|
|
4343
|
+
children: [option.name, /* @__PURE__ */ jsxs(Box, {
|
|
4344
|
+
component: "span",
|
|
4345
|
+
sx: {
|
|
4346
|
+
ml: "auto",
|
|
4347
|
+
color: "text.secondary",
|
|
4348
|
+
fontSize: 11
|
|
4349
|
+
},
|
|
4350
|
+
children: [option.columns.length, " colunas"]
|
|
4351
|
+
})]
|
|
4352
|
+
}, key),
|
|
4353
|
+
renderInput: (params) => /* @__PURE__ */ jsx(TextField, {
|
|
4354
|
+
...params,
|
|
4355
|
+
label: "tabela"
|
|
4356
|
+
})
|
|
4357
|
+
}),
|
|
4358
|
+
source && /* @__PURE__ */ jsx(Autocomplete, {
|
|
4359
|
+
size: "small",
|
|
4360
|
+
options: columns.map((item) => item.name),
|
|
4361
|
+
value: column,
|
|
4362
|
+
onChange: (_, value) => setColumn(value),
|
|
4363
|
+
noOptionsText: source.source.kind === "model" ? "Sem colunas: valide o SQL dessa tabela" : "Nenhuma coluna",
|
|
4364
|
+
renderOption: ({ key, ...props }, name) => /* @__PURE__ */ jsxs(Box, {
|
|
4365
|
+
component: "li",
|
|
4366
|
+
...props,
|
|
4367
|
+
sx: {
|
|
4368
|
+
gap: 2,
|
|
4369
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4370
|
+
fontSize: 12
|
|
4371
|
+
},
|
|
4372
|
+
children: [name, /* @__PURE__ */ jsx(Box, {
|
|
4373
|
+
component: "span",
|
|
4374
|
+
sx: {
|
|
4375
|
+
ml: "auto",
|
|
4376
|
+
color: "text.secondary",
|
|
4377
|
+
fontSize: 11
|
|
4378
|
+
},
|
|
4379
|
+
children: columns.find((item) => item.name === name)?.type
|
|
4380
|
+
})]
|
|
4381
|
+
}, key),
|
|
4382
|
+
renderInput: (params) => /* @__PURE__ */ jsx(TextField, {
|
|
4383
|
+
...params,
|
|
4384
|
+
label: template.columnLabel
|
|
4385
|
+
})
|
|
4386
|
+
}),
|
|
4387
|
+
result ? /* @__PURE__ */ jsx(Box, {
|
|
4388
|
+
component: "pre",
|
|
4389
|
+
sx: {
|
|
4390
|
+
m: 0,
|
|
4391
|
+
p: 1,
|
|
4392
|
+
border: 1,
|
|
4393
|
+
borderColor: "divider",
|
|
4394
|
+
borderRadius: 1,
|
|
4395
|
+
fontFamily: MONO,
|
|
4396
|
+
fontSize: 12,
|
|
4397
|
+
maxHeight: 280,
|
|
4398
|
+
overflow: "auto"
|
|
4399
|
+
},
|
|
4400
|
+
children: /* @__PURE__ */ jsx(HighlightedSql, { code: result })
|
|
4401
|
+
}) : /* @__PURE__ */ jsx(Typography, {
|
|
4402
|
+
sx: {
|
|
4403
|
+
fontSize: 12,
|
|
4404
|
+
color: "text.secondary"
|
|
4405
|
+
},
|
|
4406
|
+
children: "Escolha a tabela e a coluna para ver o SQL."
|
|
4407
|
+
})
|
|
4408
|
+
]
|
|
4409
|
+
}),
|
|
4410
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
|
|
4411
|
+
onClick: onClose,
|
|
4412
|
+
children: "Cancelar"
|
|
4413
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
4414
|
+
variant: "contained",
|
|
4415
|
+
disabled: !result,
|
|
4416
|
+
onClick: () => {
|
|
4417
|
+
onApply(result);
|
|
4418
|
+
onClose();
|
|
4419
|
+
},
|
|
4420
|
+
title: "Substitui o SQL atual",
|
|
4421
|
+
children: "Usar no SQL"
|
|
4422
|
+
})] })
|
|
4423
|
+
] });
|
|
4424
|
+
}
|
|
4425
|
+
//#endregion
|
|
4426
|
+
//#region src/devtools/SqlEditor.tsx
|
|
4427
|
+
/**
|
|
4428
|
+
* Campo de SQL com destaque de sintaxe e autocomplete de tabelas e colunas. As colunas vêm do
|
|
4429
|
+
* `information_schema` (tabelas/views do banco) e das colunas de cada tabela do modelo (lidas do banco).
|
|
4430
|
+
* O ⤢ abre o mesmo SQL num editor grande; com `queryTools`, ele traz "Criar query" (escolher tabela e
|
|
4431
|
+
* colunas do banco), "Importar template" (calendário etc.) e "Validar SQL" (executa no banco e
|
|
4432
|
+
* mostra uma amostra).
|
|
4433
|
+
*
|
|
4434
|
+
* O destaque é um `<pre>` colorido por baixo do `<textarea>` de texto transparente: mesma fonte,
|
|
4435
|
+
* mesmo recuo e mesma quebra de linha, com a rolagem sincronizada. O cursor, a seleção e o
|
|
4436
|
+
* autocomplete continuam sendo do textarea.
|
|
4437
|
+
*/
|
|
4438
|
+
const PAD = {
|
|
4439
|
+
top: 8.5,
|
|
4440
|
+
right: 30,
|
|
4441
|
+
bottom: 8.5,
|
|
4442
|
+
left: 12
|
|
4443
|
+
};
|
|
4444
|
+
const LINE_HEIGHT = "18px";
|
|
4445
|
+
const SqlCompletionsContext = createContext({
|
|
4446
|
+
tables: [],
|
|
4447
|
+
modelColumns: {}
|
|
4448
|
+
});
|
|
4449
|
+
const KEYWORDS = new Set("WHERE ON LEFT RIGHT INNER OUTER JOIN GROUP ORDER LIMIT USING AND OR AS SET HAVING UNION".split(" "));
|
|
4450
|
+
/** `alias -> tabela` a partir dos `FROM x a` / `JOIN x AS a` do texto. */
|
|
4451
|
+
function aliases(sql) {
|
|
4452
|
+
const map = /* @__PURE__ */ new Map();
|
|
4453
|
+
for (const match of sql.matchAll(/\b(?:FROM|JOIN)\s+`?(\w+)`?(?:\s+(?:AS\s+)?`?(\w+)`?)?/gi)) {
|
|
4454
|
+
const [, table, alias] = match;
|
|
4455
|
+
map.set(table.toLowerCase(), table);
|
|
4456
|
+
if (alias && !KEYWORDS.has(alias.toUpperCase())) map.set(alias.toLowerCase(), table);
|
|
4457
|
+
}
|
|
4458
|
+
return map;
|
|
4459
|
+
}
|
|
4460
|
+
function suggest(sql, caret, completions) {
|
|
4461
|
+
const before = sql.slice(0, caret);
|
|
4462
|
+
const tables = new Map(completions.tables.map((table) => [table.name.toLowerCase(), table]));
|
|
4463
|
+
const columnsOf = (table) => tables.get(table.toLowerCase())?.columns.map((column) => ({
|
|
4464
|
+
text: column.name,
|
|
4465
|
+
detail: column.type
|
|
4466
|
+
})) ?? [];
|
|
4467
|
+
let prefix;
|
|
4468
|
+
let candidates;
|
|
4469
|
+
const qualified = /([A-Za-z_]\w*)\.(\w*)$/.exec(before);
|
|
4470
|
+
if (qualified) {
|
|
4471
|
+
const [, qualifier, typed] = qualified;
|
|
4472
|
+
prefix = typed;
|
|
4473
|
+
if (completions.modelColumns[qualifier]) candidates = completions.modelColumns[qualifier].map((text) => ({
|
|
4474
|
+
text,
|
|
4475
|
+
detail: qualifier
|
|
4476
|
+
}));
|
|
4477
|
+
else candidates = columnsOf(aliases(sql).get(qualifier.toLowerCase()) ?? qualifier);
|
|
4478
|
+
} else {
|
|
4479
|
+
const word = /[A-Za-z_]\w*$/.exec(before);
|
|
4480
|
+
if (!word || word[0].length < 2) return null;
|
|
4481
|
+
prefix = word[0];
|
|
4482
|
+
const referenced = [...new Set(aliases(sql).values())];
|
|
4483
|
+
candidates = [
|
|
4484
|
+
...Object.keys(completions.modelColumns).map((text) => ({
|
|
4485
|
+
text,
|
|
4486
|
+
detail: "tabela do modelo"
|
|
4487
|
+
})),
|
|
4488
|
+
...completions.tables.map((table) => ({
|
|
4489
|
+
text: table.name,
|
|
4490
|
+
detail: table.type
|
|
4491
|
+
})),
|
|
4492
|
+
...referenced.flatMap(columnsOf)
|
|
4493
|
+
];
|
|
4494
|
+
}
|
|
4495
|
+
const lower = prefix.toLowerCase();
|
|
4496
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4497
|
+
const items = candidates.filter((item) => {
|
|
4498
|
+
const text = item.text.toLowerCase();
|
|
4499
|
+
if (seen.has(text) || text === lower || !text.includes(lower)) return false;
|
|
4500
|
+
seen.add(text);
|
|
4501
|
+
return true;
|
|
4502
|
+
}).sort((a, b) => Number(!a.text.toLowerCase().startsWith(lower)) - Number(!b.text.toLowerCase().startsWith(lower)) || a.text.localeCompare(b.text)).slice(0, 8);
|
|
4503
|
+
return items.length ? {
|
|
4504
|
+
from: caret - prefix.length,
|
|
4505
|
+
items
|
|
4506
|
+
} : null;
|
|
4507
|
+
}
|
|
4508
|
+
function SqlEditor({ value, onChange, label, placeholder = "SQL", minRows, maxRows = 6, error, warning, expandable = true, queryTools = false }) {
|
|
4509
|
+
const completions = useContext(SqlCompletionsContext);
|
|
4510
|
+
const anchor = useRef(null);
|
|
4511
|
+
const input = useRef(null);
|
|
4512
|
+
const highlight = useRef(null);
|
|
4513
|
+
const [expanded, setExpanded] = useState(false);
|
|
4514
|
+
const [building, setBuilding] = useState(false);
|
|
4515
|
+
const [importing, setImporting] = useState(false);
|
|
4516
|
+
const [test, setTest] = useState(null);
|
|
4517
|
+
const [testing, setTesting] = useState(false);
|
|
4518
|
+
const currentTest = test?.sql === value ? test : null;
|
|
4519
|
+
const testSql = completions.testSql;
|
|
4520
|
+
const runTest = async () => {
|
|
4521
|
+
if (!testSql) return;
|
|
4522
|
+
const sql = value;
|
|
4523
|
+
setTesting(true);
|
|
4524
|
+
try {
|
|
4525
|
+
setTest({
|
|
4526
|
+
sql,
|
|
4527
|
+
result: await testSql(sql)
|
|
4528
|
+
});
|
|
4529
|
+
} catch (error) {
|
|
4530
|
+
setTest({
|
|
4531
|
+
sql,
|
|
4532
|
+
error: errorMessage(error)
|
|
4533
|
+
});
|
|
4534
|
+
} finally {
|
|
4535
|
+
setTesting(false);
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4538
|
+
const [state, setState] = useState(null);
|
|
4539
|
+
const accept = (item) => {
|
|
4540
|
+
const element = input.current;
|
|
4541
|
+
if (!element || !state) return;
|
|
4542
|
+
const caret = element.selectionStart;
|
|
4543
|
+
onChange(value.slice(0, state.from) + item.text + value.slice(caret));
|
|
4544
|
+
setState(null);
|
|
4545
|
+
const position = state.from + item.text.length;
|
|
4546
|
+
requestAnimationFrame(() => {
|
|
4547
|
+
element.focus();
|
|
4548
|
+
element.setSelectionRange(position, position);
|
|
4549
|
+
});
|
|
4550
|
+
};
|
|
4551
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4552
|
+
/* @__PURE__ */ jsx(TextField, {
|
|
4553
|
+
ref: anchor,
|
|
4554
|
+
inputRef: input,
|
|
4555
|
+
size: "small",
|
|
4556
|
+
fullWidth: true,
|
|
4557
|
+
multiline: true,
|
|
4558
|
+
label,
|
|
4559
|
+
minRows,
|
|
4560
|
+
maxRows,
|
|
4561
|
+
value,
|
|
4562
|
+
placeholder,
|
|
4563
|
+
error: !value.trim() || !!error,
|
|
4564
|
+
helperText: error ?? warning,
|
|
4565
|
+
onChange: (event) => {
|
|
4566
|
+
onChange(event.target.value);
|
|
4567
|
+
const found = suggest(event.target.value, event.target.selectionStart ?? 0, completions);
|
|
4568
|
+
setState(found && {
|
|
4569
|
+
...found,
|
|
4570
|
+
index: 0
|
|
4571
|
+
});
|
|
4572
|
+
},
|
|
4573
|
+
onKeyDown: (event) => {
|
|
4574
|
+
if (!state) return;
|
|
4575
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
4576
|
+
event.preventDefault();
|
|
4577
|
+
const step = event.key === "ArrowDown" ? 1 : -1;
|
|
4578
|
+
setState({
|
|
4579
|
+
...state,
|
|
4580
|
+
index: (state.index + step + state.items.length) % state.items.length
|
|
4581
|
+
});
|
|
4582
|
+
} else if (event.key === "Enter" || event.key === "Tab") {
|
|
4583
|
+
event.preventDefault();
|
|
4584
|
+
accept(state.items[state.index]);
|
|
4585
|
+
} else if (event.key === "Escape") {
|
|
4586
|
+
event.stopPropagation();
|
|
4587
|
+
setState(null);
|
|
4588
|
+
}
|
|
4589
|
+
},
|
|
4590
|
+
onBlur: () => setState(null),
|
|
4591
|
+
slotProps: {
|
|
4592
|
+
htmlInput: {
|
|
4593
|
+
spellCheck: false,
|
|
4594
|
+
onScroll: (event) => {
|
|
4595
|
+
if (highlight.current) highlight.current.scrollTop = event.currentTarget.scrollTop;
|
|
4596
|
+
}
|
|
4597
|
+
},
|
|
4598
|
+
input: { endAdornment: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Box, {
|
|
4599
|
+
component: "pre",
|
|
4600
|
+
ref: highlight,
|
|
4601
|
+
"aria-hidden": true,
|
|
4602
|
+
sx: {
|
|
4603
|
+
...textSx,
|
|
4604
|
+
m: 0,
|
|
4605
|
+
p: 0,
|
|
4606
|
+
position: "absolute",
|
|
4607
|
+
top: PAD.top,
|
|
4608
|
+
right: PAD.right,
|
|
4609
|
+
bottom: PAD.bottom,
|
|
4610
|
+
left: PAD.left,
|
|
4611
|
+
overflow: "hidden",
|
|
4612
|
+
pointerEvents: "none",
|
|
4613
|
+
color: "text.primary"
|
|
4614
|
+
},
|
|
4615
|
+
children: [/* @__PURE__ */ jsx(HighlightedSql, { code: value }), "\n"]
|
|
4616
|
+
}), expandable && /* @__PURE__ */ jsx(Box, {
|
|
4617
|
+
component: "button",
|
|
4618
|
+
type: "button",
|
|
4619
|
+
title: "Expandir o SQL",
|
|
4620
|
+
"aria-label": "Expandir o SQL",
|
|
4621
|
+
onClick: () => setExpanded(true),
|
|
4622
|
+
sx: {
|
|
4623
|
+
position: "absolute",
|
|
4624
|
+
top: 4,
|
|
4625
|
+
right: 4,
|
|
4626
|
+
zIndex: 2,
|
|
4627
|
+
border: 0,
|
|
4628
|
+
borderRadius: .5,
|
|
4629
|
+
bgcolor: "transparent",
|
|
4630
|
+
color: "text.secondary",
|
|
4631
|
+
cursor: "pointer",
|
|
4004
4632
|
fontSize: 14,
|
|
4005
4633
|
lineHeight: 1,
|
|
4006
4634
|
p: .25,
|
|
@@ -4046,25 +4674,90 @@ function SqlEditor({ value, onChange, label, placeholder = "SQL", minRows, maxRo
|
|
|
4046
4674
|
sx: { fontSize: 14 },
|
|
4047
4675
|
children: label ?? "SQL"
|
|
4048
4676
|
}),
|
|
4049
|
-
/* @__PURE__ */
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4677
|
+
/* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
4678
|
+
/* @__PURE__ */ jsx(Box, {
|
|
4679
|
+
sx: { pt: 1 },
|
|
4680
|
+
children: /* @__PURE__ */ jsx(SqlEditor, {
|
|
4681
|
+
value,
|
|
4682
|
+
onChange,
|
|
4683
|
+
placeholder,
|
|
4684
|
+
minRows: 20,
|
|
4685
|
+
maxRows: 36,
|
|
4686
|
+
error,
|
|
4687
|
+
warning,
|
|
4688
|
+
expandable: false
|
|
4689
|
+
})
|
|
4690
|
+
}),
|
|
4691
|
+
currentTest?.error && /* @__PURE__ */ jsx(Alert, {
|
|
4692
|
+
severity: "error",
|
|
4693
|
+
sx: {
|
|
4694
|
+
mt: 1,
|
|
4695
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
4696
|
+
fontSize: 12
|
|
4697
|
+
},
|
|
4698
|
+
children: currentTest.error
|
|
4699
|
+
}),
|
|
4700
|
+
currentTest?.result && /* @__PURE__ */ jsxs(Box, {
|
|
4701
|
+
sx: {
|
|
4702
|
+
mt: 1,
|
|
4703
|
+
display: "flex",
|
|
4704
|
+
flexDirection: "column",
|
|
4705
|
+
gap: 1
|
|
4706
|
+
},
|
|
4707
|
+
children: [/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Chip, {
|
|
4708
|
+
color: "success",
|
|
4709
|
+
size: "small",
|
|
4710
|
+
label: `SQL válido · ${currentTest.result.columns.length} colunas · ${currentTest.result.durationMs} ms`
|
|
4711
|
+
}) }), /* @__PURE__ */ jsx(ResultTable, {
|
|
4712
|
+
rows: currentTest.result.rows,
|
|
4713
|
+
maxHeight: 240
|
|
4714
|
+
})]
|
|
4060
4715
|
})
|
|
4061
|
-
|
|
4062
|
-
/* @__PURE__ */
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4716
|
+
] }),
|
|
4717
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [
|
|
4718
|
+
queryTools && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4719
|
+
/* @__PURE__ */ jsx(Button, {
|
|
4720
|
+
onClick: () => setBuilding(true),
|
|
4721
|
+
title: "Escolher a tabela e as colunas",
|
|
4722
|
+
children: "Criar query"
|
|
4723
|
+
}),
|
|
4724
|
+
/* @__PURE__ */ jsx(Button, {
|
|
4725
|
+
onClick: () => setImporting(true),
|
|
4726
|
+
title: "Calendário e outras queries prontas",
|
|
4727
|
+
children: "Importar template"
|
|
4728
|
+
}),
|
|
4729
|
+
testSql && /* @__PURE__ */ jsx(Button, {
|
|
4730
|
+
disabled: !value.trim() || testing,
|
|
4731
|
+
onClick: runTest,
|
|
4732
|
+
title: "Executa o SQL no banco e mostra as primeiras linhas",
|
|
4733
|
+
children: testing ? "Validando…" : "Validar SQL"
|
|
4734
|
+
})
|
|
4735
|
+
] }),
|
|
4736
|
+
/* @__PURE__ */ jsx(Box, { sx: { flex: 1 } }),
|
|
4737
|
+
/* @__PURE__ */ jsx(Button, {
|
|
4738
|
+
onClick: () => setExpanded(false),
|
|
4739
|
+
children: "Fechar"
|
|
4740
|
+
})
|
|
4741
|
+
] })
|
|
4066
4742
|
]
|
|
4067
4743
|
}),
|
|
4744
|
+
queryTools && /* @__PURE__ */ jsx(QueryBuilder, {
|
|
4745
|
+
open: building,
|
|
4746
|
+
onClose: () => setBuilding(false),
|
|
4747
|
+
tables: completions.tables,
|
|
4748
|
+
sql: value,
|
|
4749
|
+
onApply: onChange
|
|
4750
|
+
}),
|
|
4751
|
+
queryTools && /* @__PURE__ */ jsx(QueryTemplates, {
|
|
4752
|
+
open: importing,
|
|
4753
|
+
onClose: () => setImporting(false),
|
|
4754
|
+
tables: completions.tables,
|
|
4755
|
+
modelTables: Object.fromEntries(Object.entries(completions.modelSql ?? {}).map(([name, sql]) => [name, {
|
|
4756
|
+
sql,
|
|
4757
|
+
columns: completions.modelColumns[name] ?? []
|
|
4758
|
+
}])),
|
|
4759
|
+
onApply: onChange
|
|
4760
|
+
}),
|
|
4068
4761
|
/* @__PURE__ */ jsx(Popper, {
|
|
4069
4762
|
open: !!state,
|
|
4070
4763
|
anchorEl: anchor.current,
|
|
@@ -4524,6 +5217,28 @@ function DimensionSource({ row, editor, columns, error, warning, onPatch }) {
|
|
|
4524
5217
|
}
|
|
4525
5218
|
//#endregion
|
|
4526
5219
|
//#region src/devtools/ModelDiagram.tsx
|
|
5220
|
+
const TARGET_NAME = {
|
|
5221
|
+
table: "tabela",
|
|
5222
|
+
dimension: "dimensão",
|
|
5223
|
+
measure: "medida"
|
|
5224
|
+
};
|
|
5225
|
+
/** Põe uma medida calculada (fórmula) nova, com nome livre, no fim; devolve o `uid` dela. */
|
|
5226
|
+
function addFormulaTo(setEditor) {
|
|
5227
|
+
const uid = newUid();
|
|
5228
|
+
setEditor((current) => {
|
|
5229
|
+
const taken = [...current.dimensions, ...current.measures].map((item) => item.name);
|
|
5230
|
+
const measure = emptyMeasure({
|
|
5231
|
+
uid,
|
|
5232
|
+
type: "formula",
|
|
5233
|
+
name: freeName("novaMedida", taken)
|
|
5234
|
+
});
|
|
5235
|
+
return {
|
|
5236
|
+
...current,
|
|
5237
|
+
measures: [...current.measures, measure]
|
|
5238
|
+
};
|
|
5239
|
+
});
|
|
5240
|
+
return uid;
|
|
5241
|
+
}
|
|
4527
5242
|
const CARD_WIDTH = 230;
|
|
4528
5243
|
const HEADER = 34;
|
|
4529
5244
|
const ROW = 22;
|
|
@@ -4690,6 +5405,12 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4690
5405
|
});
|
|
4691
5406
|
const [selection, setSelection] = useState(null);
|
|
4692
5407
|
const [panel, setPanel] = useState(loadPanel);
|
|
5408
|
+
const [renaming, setRenaming] = useState(null);
|
|
5409
|
+
const [menu, setMenu] = useState(null);
|
|
5410
|
+
const closeMenu = () => setMenu((current) => current && {
|
|
5411
|
+
...current,
|
|
5412
|
+
open: false
|
|
5413
|
+
});
|
|
4693
5414
|
useEffect(() => {
|
|
4694
5415
|
try {
|
|
4695
5416
|
localStorage.setItem(PANEL_KEY, JSON.stringify(panel));
|
|
@@ -4810,15 +5531,16 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4810
5531
|
const rect = canvas.current?.getBoundingClientRect();
|
|
4811
5532
|
return toWorld((rect?.left ?? 0) + (rect?.width ?? 600) / 2 - CARD_WIDTH / 2, (rect?.top ?? 0) + (rect?.height ?? 400) / 3);
|
|
4812
5533
|
};
|
|
4813
|
-
|
|
5534
|
+
/** `at`: canto do card (o ponto do botão direito); sem ele, perto do centro da tela. */
|
|
5535
|
+
const addTable = (at) => {
|
|
4814
5536
|
const uid = newUid();
|
|
4815
5537
|
const name = freeName("novaTabela", editor.tables.map((table) => table.name));
|
|
4816
|
-
const
|
|
5538
|
+
const position = at ?? visibleCenter();
|
|
4817
5539
|
setEditor((current) => ({
|
|
4818
5540
|
...current,
|
|
4819
5541
|
positions: {
|
|
4820
5542
|
...current.positions,
|
|
4821
|
-
[uid]:
|
|
5543
|
+
[uid]: position
|
|
4822
5544
|
},
|
|
4823
5545
|
tables: [...current.tables, {
|
|
4824
5546
|
uid,
|
|
@@ -4834,6 +5556,20 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4834
5556
|
uid
|
|
4835
5557
|
});
|
|
4836
5558
|
};
|
|
5559
|
+
const organize = () => {
|
|
5560
|
+
const layout = autoLayout(nodes, sizes);
|
|
5561
|
+
setPositions((current) => Object.fromEntries(Object.entries(layout).map(([id, point]) => [id, {
|
|
5562
|
+
...current[id],
|
|
5563
|
+
...point
|
|
5564
|
+
}])));
|
|
5565
|
+
fitToScreen(layout);
|
|
5566
|
+
};
|
|
5567
|
+
const addFormula = () => {
|
|
5568
|
+
setSelection({
|
|
5569
|
+
kind: "measure",
|
|
5570
|
+
uid: addFormulaTo(setEditor)
|
|
5571
|
+
});
|
|
5572
|
+
};
|
|
4837
5573
|
const removeTable = (uid) => {
|
|
4838
5574
|
setEditor((current) => ({
|
|
4839
5575
|
...current,
|
|
@@ -4842,6 +5578,19 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4842
5578
|
}));
|
|
4843
5579
|
setSelection(null);
|
|
4844
5580
|
};
|
|
5581
|
+
const remove = (target) => {
|
|
5582
|
+
if (target.kind === "table") return removeTable(target.uid);
|
|
5583
|
+
const list = {
|
|
5584
|
+
relationship: "relationships",
|
|
5585
|
+
dimension: "dimensions",
|
|
5586
|
+
measure: "measures"
|
|
5587
|
+
}[target.kind];
|
|
5588
|
+
setEditor((current) => ({
|
|
5589
|
+
...current,
|
|
5590
|
+
[list]: current[list].filter((row) => row.uid !== target.uid)
|
|
5591
|
+
}));
|
|
5592
|
+
setSelection((current) => current?.uid === target.uid ? null : current);
|
|
5593
|
+
};
|
|
4845
5594
|
const commitRename = (uid, name) => setEditor((current) => renameTable(current, uid, name));
|
|
4846
5595
|
const connect = (from, fromColumn, to, toColumn) => {
|
|
4847
5596
|
if (from.uid === to.uid) return;
|
|
@@ -4902,20 +5651,7 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
4902
5651
|
});
|
|
4903
5652
|
};
|
|
4904
5653
|
const removeSelection = () => {
|
|
4905
|
-
if (
|
|
4906
|
-
if (selection.kind === "table") removeTable(selection.uid);
|
|
4907
|
-
else {
|
|
4908
|
-
const list = {
|
|
4909
|
-
relationship: "relationships",
|
|
4910
|
-
dimension: "dimensions",
|
|
4911
|
-
measure: "measures"
|
|
4912
|
-
}[selection.kind];
|
|
4913
|
-
setEditor((current) => ({
|
|
4914
|
-
...current,
|
|
4915
|
-
[list]: current[list].filter((row) => row.uid !== selection.uid)
|
|
4916
|
-
}));
|
|
4917
|
-
setSelection(null);
|
|
4918
|
-
}
|
|
5654
|
+
if (selection) remove(selection);
|
|
4919
5655
|
};
|
|
4920
5656
|
const startPan = (event) => {
|
|
4921
5657
|
if (event.button !== 0) return;
|
|
@@ -5103,305 +5839,392 @@ function ModelDiagram({ editor, setEditor, columns, errors, warnings, loadingCol
|
|
|
5103
5839
|
height: "100%",
|
|
5104
5840
|
minHeight: 0
|
|
5105
5841
|
},
|
|
5106
|
-
children: [
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
}
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5842
|
+
children: [
|
|
5843
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5844
|
+
ref: canvas,
|
|
5845
|
+
tabIndex: -1,
|
|
5846
|
+
onMouseDown: startPan,
|
|
5847
|
+
onMouseDownCapture: () => canvas.current?.focus({ preventScroll: true }),
|
|
5848
|
+
onKeyDown: (event) => {
|
|
5849
|
+
if ((event.key === "Delete" || event.key === "Backspace") && event.target === canvas.current) removeSelection();
|
|
5850
|
+
},
|
|
5851
|
+
onWheel,
|
|
5852
|
+
onContextMenu: (event) => {
|
|
5853
|
+
event.preventDefault();
|
|
5854
|
+
setMenu({
|
|
5855
|
+
open: true,
|
|
5856
|
+
x: event.clientX,
|
|
5857
|
+
y: event.clientY,
|
|
5858
|
+
target: null
|
|
5859
|
+
});
|
|
5860
|
+
},
|
|
5861
|
+
sx: {
|
|
5862
|
+
outline: "none",
|
|
5863
|
+
flex: 1,
|
|
5864
|
+
position: "relative",
|
|
5865
|
+
overflow: "hidden",
|
|
5866
|
+
cursor: "grab",
|
|
5867
|
+
bgcolor: "#15171b",
|
|
5868
|
+
backgroundImage: "radial-gradient(rgba(255,255,255,.07) 1px, transparent 1px)",
|
|
5869
|
+
backgroundSize: `${20 * view.zoom}px ${20 * view.zoom}px`,
|
|
5870
|
+
backgroundPosition: `${view.x}px ${view.y}px`
|
|
5871
|
+
},
|
|
5872
|
+
children: [
|
|
5873
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5874
|
+
sx: {
|
|
5875
|
+
position: "absolute",
|
|
5876
|
+
left: 0,
|
|
5877
|
+
top: 0,
|
|
5878
|
+
transformOrigin: "0 0",
|
|
5879
|
+
transform: `translate(${view.x}px, ${view.y}px) scale(${view.zoom})`
|
|
5880
|
+
},
|
|
5881
|
+
children: [/* @__PURE__ */ jsxs("svg", {
|
|
5882
|
+
width: 1,
|
|
5883
|
+
height: 1,
|
|
5884
|
+
style: {
|
|
5885
|
+
position: "absolute",
|
|
5886
|
+
overflow: "visible",
|
|
5887
|
+
pointerEvents: "none"
|
|
5888
|
+
},
|
|
5889
|
+
"aria-hidden": true,
|
|
5890
|
+
children: [lines.map((line) => /* @__PURE__ */ jsx(RelationshipLine, {
|
|
5891
|
+
line,
|
|
5892
|
+
hovered: hoveredEdge === line.id,
|
|
5893
|
+
onHover: (hovered) => setHoveredEdge(hovered ? line.id : null)
|
|
5894
|
+
}, line.id)), linkPreview && /* @__PURE__ */ jsx("path", {
|
|
5895
|
+
d: linkPreview,
|
|
5896
|
+
fill: "none",
|
|
5897
|
+
stroke: "#F58220",
|
|
5898
|
+
strokeWidth: 2,
|
|
5899
|
+
strokeDasharray: "5 4"
|
|
5900
|
+
})]
|
|
5901
|
+
}), nodes.map((node) => /* @__PURE__ */ jsx(TableCard, {
|
|
5902
|
+
node,
|
|
5903
|
+
position: placed(node),
|
|
5904
|
+
size: sizeOf(node),
|
|
5905
|
+
collapsed: !!collapsed[node.id],
|
|
5906
|
+
selected: !!isSelected(node),
|
|
5907
|
+
linking: !!linking && linking.node.id !== node.id,
|
|
5908
|
+
selectedField: selection?.kind === "dimension" || selection?.kind === "measure" ? selection.uid : null,
|
|
5909
|
+
onToggle: () => setCollapsed((current) => ({
|
|
5910
|
+
...current,
|
|
5911
|
+
[node.id]: !current[node.id]
|
|
5912
|
+
})),
|
|
5913
|
+
onScroll: (top) => setScrolls((current) => ({
|
|
5914
|
+
...current,
|
|
5915
|
+
[node.id]: top
|
|
5916
|
+
})),
|
|
5917
|
+
onHeaderDown: (event) => startMove(event, node),
|
|
5918
|
+
onResizeDown: (event, edges) => startResize(event, node, edges),
|
|
5919
|
+
onResizeReset: () => resetSize(node),
|
|
5920
|
+
onColumnDown: (event, column) => startLink(event, node, column),
|
|
5921
|
+
onFieldClick: (item) => setSelection({
|
|
5922
|
+
kind: item.kind,
|
|
5923
|
+
uid: item.uid
|
|
5924
|
+
}),
|
|
5925
|
+
onAddField: (kind, column) => addField(kind, node.name, column),
|
|
5926
|
+
renaming: renaming === node.uid,
|
|
5927
|
+
onRenameStart: () => setRenaming(node.uid),
|
|
5928
|
+
onRename: (name) => {
|
|
5929
|
+
setRenaming(null);
|
|
5930
|
+
if (name !== null && name !== node.name) commitRename(node.uid, name);
|
|
5931
|
+
},
|
|
5932
|
+
onContextMenu: (event, target) => {
|
|
5933
|
+
event.preventDefault();
|
|
5934
|
+
event.stopPropagation();
|
|
5935
|
+
setMenu({
|
|
5936
|
+
open: true,
|
|
5937
|
+
x: event.clientX,
|
|
5938
|
+
y: event.clientY,
|
|
5939
|
+
target
|
|
5940
|
+
});
|
|
5941
|
+
}
|
|
5942
|
+
}, node.id))]
|
|
5943
|
+
}),
|
|
5944
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5945
|
+
onMouseDown: (event) => event.stopPropagation(),
|
|
5946
|
+
onContextMenu: (event) => event.stopPropagation(),
|
|
5947
|
+
sx: {
|
|
5948
|
+
position: "absolute",
|
|
5949
|
+
top: 8,
|
|
5950
|
+
left: 8,
|
|
5951
|
+
right: 8,
|
|
5952
|
+
display: "flex",
|
|
5953
|
+
gap: .75,
|
|
5954
|
+
flexWrap: "wrap",
|
|
5955
|
+
alignItems: "center",
|
|
5956
|
+
pointerEvents: "none",
|
|
5957
|
+
"& > *": { pointerEvents: "auto" }
|
|
5958
|
+
},
|
|
5959
|
+
children: [
|
|
5960
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5961
|
+
size: "small",
|
|
5962
|
+
variant: "outlined",
|
|
5963
|
+
onClick: () => addTable(),
|
|
5964
|
+
children: "+ Tabela"
|
|
5965
|
+
}),
|
|
5966
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5967
|
+
size: "small",
|
|
5968
|
+
onClick: onLoadColumns,
|
|
5969
|
+
disabled: loadingColumns,
|
|
5970
|
+
title: "Lê uma linha de cada tabela para listar as colunas",
|
|
5971
|
+
sx: { bgcolor: "#1b1e23" },
|
|
5972
|
+
children: loadingColumns ? "Lendo colunas…" : "Recarregar colunas"
|
|
5973
|
+
}),
|
|
5974
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5975
|
+
sx: {
|
|
5976
|
+
ml: "auto",
|
|
5977
|
+
display: "flex",
|
|
5978
|
+
gap: .5,
|
|
5979
|
+
alignItems: "center"
|
|
5980
|
+
},
|
|
5981
|
+
children: [
|
|
5982
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5983
|
+
size: "small",
|
|
5984
|
+
onClick: organize,
|
|
5985
|
+
title: "Dimensões em cima, fatos embaixo (troca as posições salvas)",
|
|
5986
|
+
sx: { bgcolor: "#1b1e23" },
|
|
5987
|
+
children: "Organizar"
|
|
5988
|
+
}),
|
|
5989
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5990
|
+
size: "small",
|
|
5991
|
+
onClick: () => fitToScreen(),
|
|
5992
|
+
sx: { bgcolor: "#1b1e23" },
|
|
5993
|
+
children: "Ajustar à tela"
|
|
5994
|
+
}),
|
|
5995
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
5996
|
+
size: "small",
|
|
5997
|
+
"aria-label": "Diminuir zoom",
|
|
5998
|
+
onClick: () => setView((current) => ({
|
|
5999
|
+
...current,
|
|
6000
|
+
zoom: Math.max(.3, current.zoom - .1)
|
|
6001
|
+
})),
|
|
6002
|
+
children: "−"
|
|
6003
|
+
}),
|
|
6004
|
+
/* @__PURE__ */ jsxs(Typography, {
|
|
6005
|
+
sx: {
|
|
6006
|
+
fontSize: 11,
|
|
6007
|
+
fontFamily: MONO,
|
|
6008
|
+
width: 36,
|
|
6009
|
+
textAlign: "center"
|
|
6010
|
+
},
|
|
6011
|
+
children: [Math.round(view.zoom * 100), "%"]
|
|
6012
|
+
}),
|
|
6013
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
6014
|
+
size: "small",
|
|
6015
|
+
"aria-label": "Aumentar zoom",
|
|
6016
|
+
onClick: () => setView((current) => ({
|
|
6017
|
+
...current,
|
|
6018
|
+
zoom: Math.min(2, current.zoom + .1)
|
|
6019
|
+
})),
|
|
6020
|
+
children: "+"
|
|
6021
|
+
})
|
|
6022
|
+
]
|
|
6023
|
+
})
|
|
6024
|
+
]
|
|
6025
|
+
}),
|
|
6026
|
+
!nodes.length && /* @__PURE__ */ jsx(Box, {
|
|
6027
|
+
sx: {
|
|
6028
|
+
position: "absolute",
|
|
6029
|
+
inset: 0,
|
|
6030
|
+
display: "flex",
|
|
6031
|
+
alignItems: "center",
|
|
6032
|
+
justifyContent: "center",
|
|
6033
|
+
pointerEvents: "none",
|
|
6034
|
+
color: "text.secondary",
|
|
6035
|
+
fontSize: 13,
|
|
6036
|
+
textAlign: "center",
|
|
6037
|
+
px: 4
|
|
6038
|
+
},
|
|
6039
|
+
children: "Modelo vazio. Adicione as tabelas dimensão (cadastros: navio, calendário…) e fato (lançamentos: pesagens, custos…) e arraste a coluna da fato até a chave da dimensão para relacioná-las."
|
|
6040
|
+
}),
|
|
6041
|
+
!!nodes.length && /* @__PURE__ */ jsx(Typography, {
|
|
6042
|
+
sx: {
|
|
5139
6043
|
position: "absolute",
|
|
5140
|
-
|
|
6044
|
+
left: 10,
|
|
6045
|
+
bottom: 6,
|
|
6046
|
+
fontSize: 11,
|
|
6047
|
+
color: "text.disabled",
|
|
5141
6048
|
pointerEvents: "none"
|
|
5142
6049
|
},
|
|
5143
|
-
"
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
[node.id]: !current[node.id]
|
|
5166
|
-
})),
|
|
5167
|
-
onScroll: (top) => setScrolls((current) => ({
|
|
5168
|
-
...current,
|
|
5169
|
-
[node.id]: top
|
|
5170
|
-
})),
|
|
5171
|
-
onHeaderDown: (event) => startMove(event, node),
|
|
5172
|
-
onResizeDown: (event, edges) => startResize(event, node, edges),
|
|
5173
|
-
onResizeReset: () => resetSize(node),
|
|
5174
|
-
onColumnDown: (event, column) => startLink(event, node, column),
|
|
5175
|
-
onFieldClick: (item) => setSelection({
|
|
5176
|
-
kind: item.kind,
|
|
5177
|
-
uid: item.uid
|
|
5178
|
-
}),
|
|
5179
|
-
onAddField: (kind, column) => addField(kind, node.name, column)
|
|
5180
|
-
}, node.id))]
|
|
5181
|
-
}),
|
|
5182
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
5183
|
-
onMouseDown: (event) => event.stopPropagation(),
|
|
5184
|
-
sx: {
|
|
5185
|
-
position: "absolute",
|
|
5186
|
-
top: 8,
|
|
5187
|
-
left: 8,
|
|
5188
|
-
right: 8,
|
|
5189
|
-
display: "flex",
|
|
5190
|
-
gap: .75,
|
|
5191
|
-
flexWrap: "wrap",
|
|
5192
|
-
alignItems: "center",
|
|
5193
|
-
pointerEvents: "none",
|
|
5194
|
-
"& > *": { pointerEvents: "auto" }
|
|
5195
|
-
},
|
|
5196
|
-
children: [
|
|
5197
|
-
/* @__PURE__ */ jsx(Button, {
|
|
5198
|
-
size: "small",
|
|
5199
|
-
variant: "outlined",
|
|
5200
|
-
onClick: addTable,
|
|
5201
|
-
children: "+ Tabela"
|
|
5202
|
-
}),
|
|
5203
|
-
/* @__PURE__ */ jsx(Button, {
|
|
5204
|
-
size: "small",
|
|
5205
|
-
onClick: onLoadColumns,
|
|
5206
|
-
disabled: loadingColumns,
|
|
5207
|
-
title: "Lê uma linha de cada tabela para listar as colunas",
|
|
5208
|
-
sx: { bgcolor: "#1b1e23" },
|
|
5209
|
-
children: loadingColumns ? "Lendo colunas…" : "Recarregar colunas"
|
|
5210
|
-
}),
|
|
5211
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
5212
|
-
sx: {
|
|
5213
|
-
ml: "auto",
|
|
5214
|
-
display: "flex",
|
|
5215
|
-
gap: .5,
|
|
5216
|
-
alignItems: "center"
|
|
5217
|
-
},
|
|
5218
|
-
children: [
|
|
5219
|
-
/* @__PURE__ */ jsx(Button, {
|
|
5220
|
-
size: "small",
|
|
5221
|
-
onClick: () => {
|
|
5222
|
-
const layout = autoLayout(nodes, sizes);
|
|
5223
|
-
setPositions((current) => Object.fromEntries(Object.entries(layout).map(([id, point]) => [id, {
|
|
5224
|
-
...current[id],
|
|
5225
|
-
...point
|
|
5226
|
-
}])));
|
|
5227
|
-
fitToScreen(layout);
|
|
5228
|
-
},
|
|
5229
|
-
title: "Dimensões em cima, fatos embaixo (troca as posições salvas)",
|
|
5230
|
-
sx: { bgcolor: "#1b1e23" },
|
|
5231
|
-
children: "Organizar"
|
|
5232
|
-
}),
|
|
5233
|
-
/* @__PURE__ */ jsx(Button, {
|
|
5234
|
-
size: "small",
|
|
5235
|
-
onClick: () => fitToScreen(),
|
|
5236
|
-
sx: { bgcolor: "#1b1e23" },
|
|
5237
|
-
children: "Ajustar à tela"
|
|
5238
|
-
}),
|
|
5239
|
-
/* @__PURE__ */ jsx(IconButton, {
|
|
5240
|
-
size: "small",
|
|
5241
|
-
"aria-label": "Diminuir zoom",
|
|
5242
|
-
onClick: () => setView((current) => ({
|
|
5243
|
-
...current,
|
|
5244
|
-
zoom: Math.max(.3, current.zoom - .1)
|
|
5245
|
-
})),
|
|
5246
|
-
children: "−"
|
|
5247
|
-
}),
|
|
5248
|
-
/* @__PURE__ */ jsxs(Typography, {
|
|
5249
|
-
sx: {
|
|
5250
|
-
fontSize: 11,
|
|
5251
|
-
fontFamily: MONO,
|
|
5252
|
-
width: 36,
|
|
5253
|
-
textAlign: "center"
|
|
5254
|
-
},
|
|
5255
|
-
children: [Math.round(view.zoom * 100), "%"]
|
|
5256
|
-
}),
|
|
5257
|
-
/* @__PURE__ */ jsx(IconButton, {
|
|
5258
|
-
size: "small",
|
|
5259
|
-
"aria-label": "Aumentar zoom",
|
|
5260
|
-
onClick: () => setView((current) => ({
|
|
5261
|
-
...current,
|
|
5262
|
-
zoom: Math.min(2, current.zoom + .1)
|
|
5263
|
-
})),
|
|
5264
|
-
children: "+"
|
|
5265
|
-
})
|
|
5266
|
-
]
|
|
5267
|
-
})
|
|
5268
|
-
]
|
|
5269
|
-
}),
|
|
5270
|
-
!nodes.length && /* @__PURE__ */ jsx(Box, {
|
|
5271
|
-
sx: {
|
|
5272
|
-
position: "absolute",
|
|
5273
|
-
inset: 0,
|
|
5274
|
-
display: "flex",
|
|
5275
|
-
alignItems: "center",
|
|
5276
|
-
justifyContent: "center",
|
|
5277
|
-
pointerEvents: "none",
|
|
5278
|
-
color: "text.secondary",
|
|
5279
|
-
fontSize: 13,
|
|
5280
|
-
textAlign: "center",
|
|
5281
|
-
px: 4
|
|
6050
|
+
children: "Arraste uma coluna até a coluna de outra tabela para relacionar · +◇/+Σ na coluna cria dimensão/medida · Duplo clique no nome renomeia · Botão direito: editar/remover (no fundo: nova tabela/medida, recarregar, organizar) · Delete remove · Ctrl+roda: zoom"
|
|
6051
|
+
})
|
|
6052
|
+
]
|
|
6053
|
+
}),
|
|
6054
|
+
/* @__PURE__ */ jsx(Menu, {
|
|
6055
|
+
open: !!menu?.open,
|
|
6056
|
+
onClose: closeMenu,
|
|
6057
|
+
anchorReference: "anchorPosition",
|
|
6058
|
+
anchorPosition: menu ? {
|
|
6059
|
+
top: menu.y,
|
|
6060
|
+
left: menu.x
|
|
6061
|
+
} : void 0,
|
|
6062
|
+
transitionDuration: {
|
|
6063
|
+
enter: 90,
|
|
6064
|
+
exit: 60
|
|
6065
|
+
},
|
|
6066
|
+
onContextMenu: (event) => event.preventDefault(),
|
|
6067
|
+
children: menu?.target ? [/* @__PURE__ */ jsxs(MenuItem, {
|
|
6068
|
+
dense: true,
|
|
6069
|
+
onClick: () => {
|
|
6070
|
+
setSelection(menu.target);
|
|
6071
|
+
closeMenu();
|
|
5282
6072
|
},
|
|
5283
|
-
children: "
|
|
5284
|
-
}),
|
|
5285
|
-
|
|
5286
|
-
sx: {
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
fontSize: 11,
|
|
5291
|
-
color: "text.disabled",
|
|
5292
|
-
pointerEvents: "none"
|
|
6073
|
+
children: ["Editar ", TARGET_NAME[menu.target.kind]]
|
|
6074
|
+
}, "edit"), /* @__PURE__ */ jsxs(MenuItem, {
|
|
6075
|
+
dense: true,
|
|
6076
|
+
sx: { color: "error.main" },
|
|
6077
|
+
onClick: () => {
|
|
6078
|
+
if (menu.target) remove(menu.target);
|
|
6079
|
+
closeMenu();
|
|
5293
6080
|
},
|
|
5294
|
-
children: "
|
|
5295
|
-
})
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
"
|
|
5321
|
-
|
|
5322
|
-
|
|
6081
|
+
children: ["Remover ", TARGET_NAME[menu.target.kind]]
|
|
6082
|
+
}, "remove")] : [
|
|
6083
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
6084
|
+
dense: true,
|
|
6085
|
+
onClick: () => {
|
|
6086
|
+
if (menu) addTable(toWorld(menu.x, menu.y));
|
|
6087
|
+
closeMenu();
|
|
6088
|
+
},
|
|
6089
|
+
children: "Nova Tabela"
|
|
6090
|
+
}, "table"),
|
|
6091
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
6092
|
+
dense: true,
|
|
6093
|
+
onClick: () => {
|
|
6094
|
+
addFormula();
|
|
6095
|
+
closeMenu();
|
|
6096
|
+
},
|
|
6097
|
+
children: "Nova Medida Calculada"
|
|
6098
|
+
}, "formula"),
|
|
6099
|
+
/* @__PURE__ */ jsx(Divider, {}, "divider"),
|
|
6100
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
6101
|
+
dense: true,
|
|
6102
|
+
disabled: loadingColumns,
|
|
6103
|
+
onClick: () => {
|
|
6104
|
+
onLoadColumns();
|
|
6105
|
+
closeMenu();
|
|
6106
|
+
},
|
|
6107
|
+
children: "Recarregar colunas"
|
|
6108
|
+
}, "columns"),
|
|
6109
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
6110
|
+
dense: true,
|
|
6111
|
+
onClick: () => {
|
|
6112
|
+
organize();
|
|
6113
|
+
closeMenu();
|
|
6114
|
+
},
|
|
6115
|
+
children: "Organizar"
|
|
6116
|
+
}, "organize")
|
|
6117
|
+
]
|
|
6118
|
+
}),
|
|
6119
|
+
panel.open ? /* @__PURE__ */ jsxs(Box, {
|
|
6120
|
+
sx: {
|
|
6121
|
+
width: panel.width,
|
|
6122
|
+
flexShrink: 0,
|
|
6123
|
+
borderLeft: 1,
|
|
6124
|
+
borderColor: "divider",
|
|
6125
|
+
position: "relative",
|
|
6126
|
+
display: "flex",
|
|
6127
|
+
flexDirection: "column",
|
|
6128
|
+
minHeight: 0
|
|
6129
|
+
},
|
|
6130
|
+
children: [
|
|
6131
|
+
/* @__PURE__ */ jsx(Box, {
|
|
6132
|
+
onMouseDown: startPanelResize,
|
|
6133
|
+
title: "Arraste para redimensionar",
|
|
6134
|
+
sx: {
|
|
6135
|
+
position: "absolute",
|
|
6136
|
+
left: -3,
|
|
6137
|
+
top: 0,
|
|
6138
|
+
bottom: 0,
|
|
6139
|
+
width: 6,
|
|
6140
|
+
cursor: "ew-resize",
|
|
6141
|
+
zIndex: 2,
|
|
6142
|
+
"&:hover": {
|
|
6143
|
+
bgcolor: "primary.main",
|
|
6144
|
+
opacity: .4
|
|
6145
|
+
}
|
|
5323
6146
|
}
|
|
5324
|
-
}
|
|
5325
|
-
|
|
5326
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
5327
|
-
sx: {
|
|
5328
|
-
display: "flex",
|
|
5329
|
-
alignItems: "center",
|
|
5330
|
-
px: 1.5,
|
|
5331
|
-
py: .5,
|
|
5332
|
-
borderBottom: 1,
|
|
5333
|
-
borderColor: "divider"
|
|
5334
|
-
},
|
|
5335
|
-
children: [/* @__PURE__ */ jsx(Typography, {
|
|
6147
|
+
}),
|
|
6148
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
5336
6149
|
sx: {
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
6150
|
+
display: "flex",
|
|
6151
|
+
alignItems: "center",
|
|
6152
|
+
px: 1.5,
|
|
6153
|
+
py: .5,
|
|
6154
|
+
borderBottom: 1,
|
|
6155
|
+
borderColor: "divider"
|
|
5341
6156
|
},
|
|
5342
|
-
children:
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
})
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
6157
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
6158
|
+
sx: {
|
|
6159
|
+
fontSize: 12,
|
|
6160
|
+
fontWeight: 700,
|
|
6161
|
+
color: "text.secondary",
|
|
6162
|
+
flex: 1
|
|
6163
|
+
},
|
|
6164
|
+
children: "Propriedades"
|
|
6165
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
6166
|
+
size: "small",
|
|
6167
|
+
"aria-label": "Recolher propriedades",
|
|
6168
|
+
title: "Recolher",
|
|
6169
|
+
onClick: () => setPanel((current) => ({
|
|
6170
|
+
...current,
|
|
6171
|
+
open: false
|
|
6172
|
+
})),
|
|
6173
|
+
children: "⟩"
|
|
6174
|
+
})]
|
|
6175
|
+
}),
|
|
6176
|
+
/* @__PURE__ */ jsx(Box, {
|
|
6177
|
+
sx: {
|
|
6178
|
+
overflow: "auto",
|
|
6179
|
+
flex: 1,
|
|
6180
|
+
p: 1.5
|
|
6181
|
+
},
|
|
6182
|
+
children: /* @__PURE__ */ jsx(Properties, {
|
|
6183
|
+
editor,
|
|
6184
|
+
selection,
|
|
6185
|
+
columns,
|
|
6186
|
+
errors,
|
|
6187
|
+
warnings,
|
|
6188
|
+
usage,
|
|
6189
|
+
onSelect: setSelection,
|
|
6190
|
+
onTry,
|
|
6191
|
+
setEditor,
|
|
6192
|
+
patchTable,
|
|
6193
|
+
patchRelationship,
|
|
6194
|
+
patchDimension,
|
|
6195
|
+
patchMeasure,
|
|
6196
|
+
removeTable,
|
|
6197
|
+
commitRename,
|
|
6198
|
+
addField,
|
|
6199
|
+
unplaced
|
|
6200
|
+
})
|
|
5378
6201
|
})
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
6202
|
+
]
|
|
6203
|
+
}) : /* @__PURE__ */ jsx(Box, {
|
|
6204
|
+
component: "button",
|
|
6205
|
+
type: "button",
|
|
6206
|
+
title: "Mostrar as propriedades",
|
|
6207
|
+
onClick: () => setPanel((current) => ({
|
|
6208
|
+
...current,
|
|
6209
|
+
open: true
|
|
6210
|
+
})),
|
|
6211
|
+
sx: {
|
|
6212
|
+
width: 28,
|
|
6213
|
+
flexShrink: 0,
|
|
6214
|
+
border: 0,
|
|
6215
|
+
borderLeft: 1,
|
|
6216
|
+
borderColor: "divider",
|
|
6217
|
+
bgcolor: "background.paper",
|
|
6218
|
+
color: "text.secondary",
|
|
6219
|
+
cursor: "pointer",
|
|
6220
|
+
writingMode: "vertical-rl",
|
|
6221
|
+
fontSize: 12,
|
|
6222
|
+
py: 1,
|
|
6223
|
+
"&:hover": { color: "primary.main" }
|
|
6224
|
+
},
|
|
6225
|
+
children: "⟨ Propriedades"
|
|
6226
|
+
})
|
|
6227
|
+
]
|
|
5405
6228
|
});
|
|
5406
6229
|
}
|
|
5407
6230
|
/** Curva com tangentes horizontais (saída e chegada pelas laterais dos cards). */
|
|
@@ -5470,11 +6293,15 @@ function RelationshipLine({ line, hovered, onHover }) {
|
|
|
5470
6293
|
]
|
|
5471
6294
|
});
|
|
5472
6295
|
}
|
|
5473
|
-
function TableCard({ node, position, size, collapsed, selected, linking, selectedField, onToggle, onScroll, onHeaderDown, onResizeDown, onResizeReset, onColumnDown, onFieldClick, onAddField }) {
|
|
6296
|
+
function TableCard({ node, position, size, collapsed, selected, linking, selectedField, onToggle, onScroll, onHeaderDown, onResizeDown, onResizeReset, onColumnDown, onFieldClick, onAddField, renaming, onRenameStart, onRename, onContextMenu }) {
|
|
5474
6297
|
const style = ROLE_STYLE[node.role];
|
|
5475
6298
|
const columnActions = ["dimension", "measure"];
|
|
5476
6299
|
return /* @__PURE__ */ jsxs(Box, {
|
|
5477
6300
|
onMouseDown: (event) => event.stopPropagation(),
|
|
6301
|
+
onContextMenu: (event) => onContextMenu(event, {
|
|
6302
|
+
kind: "table",
|
|
6303
|
+
uid: node.uid
|
|
6304
|
+
}),
|
|
5478
6305
|
sx: {
|
|
5479
6306
|
position: "absolute",
|
|
5480
6307
|
left: position.x,
|
|
@@ -5515,7 +6342,12 @@ function TableCard({ node, position, size, collapsed, selected, linking, selecte
|
|
|
5515
6342
|
},
|
|
5516
6343
|
children: "▦"
|
|
5517
6344
|
}),
|
|
5518
|
-
/* @__PURE__ */ jsx(
|
|
6345
|
+
renaming ? /* @__PURE__ */ jsx(TableNameInput, {
|
|
6346
|
+
value: node.name,
|
|
6347
|
+
onDone: onRename
|
|
6348
|
+
}) : /* @__PURE__ */ jsx(Typography, {
|
|
6349
|
+
onDoubleClick: onRenameStart,
|
|
6350
|
+
title: "Duplo clique para renomear",
|
|
5519
6351
|
sx: {
|
|
5520
6352
|
fontWeight: 700,
|
|
5521
6353
|
fontSize: 13,
|
|
@@ -5643,6 +6475,10 @@ function TableCard({ node, position, size, collapsed, selected, linking, selecte
|
|
|
5643
6475
|
}, `c:${item.name}`) : /* @__PURE__ */ jsxs(Box, {
|
|
5644
6476
|
onMouseDown: (event) => event.stopPropagation(),
|
|
5645
6477
|
onClick: () => onFieldClick(item),
|
|
6478
|
+
onContextMenu: (event) => onContextMenu(event, {
|
|
6479
|
+
kind: item.kind,
|
|
6480
|
+
uid: item.uid
|
|
6481
|
+
}),
|
|
5646
6482
|
sx: {
|
|
5647
6483
|
height: ROW,
|
|
5648
6484
|
display: "flex",
|
|
@@ -5738,23 +6574,60 @@ const resizeHandleSx = {
|
|
|
5738
6574
|
position: "absolute",
|
|
5739
6575
|
zIndex: 1
|
|
5740
6576
|
};
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
6577
|
+
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
6578
|
+
/** Nome da tabela editado no cabeçalho do card: Enter/sair confirma, Esc cancela. */
|
|
6579
|
+
function TableNameInput({ value, onDone }) {
|
|
6580
|
+
const [text, setText] = useState(value);
|
|
6581
|
+
const valid = IDENTIFIER.test(text);
|
|
6582
|
+
const done = useRef(false);
|
|
6583
|
+
const finish = (name) => {
|
|
6584
|
+
if (done.current) return;
|
|
6585
|
+
done.current = true;
|
|
6586
|
+
onDone(name);
|
|
6587
|
+
};
|
|
6588
|
+
return /* @__PURE__ */ jsx(Box, {
|
|
6589
|
+
component: "input",
|
|
6590
|
+
autoFocus: true,
|
|
6591
|
+
value: text,
|
|
6592
|
+
"aria-label": "Nome da tabela",
|
|
6593
|
+
onFocus: (event) => event.currentTarget.select(),
|
|
6594
|
+
onChange: (event) => setText(event.target.value),
|
|
6595
|
+
onMouseDown: (event) => event.stopPropagation(),
|
|
6596
|
+
onKeyDown: (event) => {
|
|
6597
|
+
event.stopPropagation();
|
|
6598
|
+
if (event.key === "Enter") finish(valid ? text : null);
|
|
6599
|
+
if (event.key === "Escape") finish(null);
|
|
5748
6600
|
},
|
|
5749
|
-
|
|
5750
|
-
|
|
6601
|
+
onBlur: () => finish(valid ? text : null),
|
|
6602
|
+
sx: {
|
|
6603
|
+
flex: 1,
|
|
6604
|
+
minWidth: 0,
|
|
6605
|
+
font: "inherit",
|
|
6606
|
+
fontSize: 13,
|
|
6607
|
+
fontWeight: 700,
|
|
6608
|
+
color: "text.primary",
|
|
6609
|
+
bgcolor: "#1b1e23",
|
|
6610
|
+
border: "1px solid",
|
|
6611
|
+
borderColor: valid ? "primary.main" : "error.main",
|
|
6612
|
+
borderRadius: .5,
|
|
6613
|
+
px: .5,
|
|
6614
|
+
outline: "none"
|
|
6615
|
+
}
|
|
6616
|
+
});
|
|
6617
|
+
}
|
|
6618
|
+
/** Bloco do painel; com `collapseKey`, o título recolhe/expande (lembrado no navegador). */
|
|
6619
|
+
function PropertyBlock({ title, collapseKey, children }) {
|
|
6620
|
+
return /* @__PURE__ */ jsx(Section, {
|
|
6621
|
+
title,
|
|
6622
|
+
collapseKey,
|
|
6623
|
+
children: /* @__PURE__ */ jsx(Box, {
|
|
5751
6624
|
sx: {
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
6625
|
+
display: "flex",
|
|
6626
|
+
flexDirection: "column",
|
|
6627
|
+
gap: 1
|
|
5755
6628
|
},
|
|
5756
|
-
children
|
|
5757
|
-
})
|
|
6629
|
+
children
|
|
6630
|
+
})
|
|
5758
6631
|
});
|
|
5759
6632
|
}
|
|
5760
6633
|
function ColumnSelect({ label, value, options, onChange }) {
|
|
@@ -5818,29 +6691,23 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
5818
6691
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5819
6692
|
/* @__PURE__ */ jsxs(PropertyBlock, {
|
|
5820
6693
|
title: "Tabela",
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
}),
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
minRows: 3,
|
|
5835
|
-
maxRows: 12,
|
|
5836
|
-
value: table.sql,
|
|
5837
|
-
error: errors[`table:${table.name}`],
|
|
5838
|
-
onChange: (sql) => patchTable(table.uid, { sql })
|
|
5839
|
-
})
|
|
5840
|
-
]
|
|
6694
|
+
collapseKey: "diagram:table",
|
|
6695
|
+
children: [/* @__PURE__ */ jsx(RenameField, {
|
|
6696
|
+
value: table.name,
|
|
6697
|
+
onCommit: (name) => commitRename(table.uid, name)
|
|
6698
|
+
}, table.uid), /* @__PURE__ */ jsx(SqlEditor, {
|
|
6699
|
+
label: "sql",
|
|
6700
|
+
minRows: 3,
|
|
6701
|
+
maxRows: 12,
|
|
6702
|
+
value: table.sql,
|
|
6703
|
+
error: errors[`table:${table.name}`],
|
|
6704
|
+
onChange: (sql) => patchTable(table.uid, { sql }),
|
|
6705
|
+
queryTools: true
|
|
6706
|
+
})]
|
|
5841
6707
|
}),
|
|
5842
6708
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
5843
6709
|
title: "Dimensões (marque as colunas)",
|
|
6710
|
+
collapseKey: "diagram:dimensions",
|
|
5844
6711
|
children: /* @__PURE__ */ jsx(DimensionPicker, {
|
|
5845
6712
|
editor,
|
|
5846
6713
|
setEditor,
|
|
@@ -5854,6 +6721,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
5854
6721
|
}),
|
|
5855
6722
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
5856
6723
|
title: "Dicionário",
|
|
6724
|
+
collapseKey: "diagram:dictionary",
|
|
5857
6725
|
children: /* @__PURE__ */ jsx(DictionaryFields, {
|
|
5858
6726
|
row: table,
|
|
5859
6727
|
onPatch: (changes) => patchTable(table.uid, changes)
|
|
@@ -6040,6 +6908,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6040
6908
|
}),
|
|
6041
6909
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
6042
6910
|
title: "Dicionário",
|
|
6911
|
+
collapseKey: "diagram:dictionary",
|
|
6043
6912
|
children: /* @__PURE__ */ jsx(DictionaryFields, {
|
|
6044
6913
|
row,
|
|
6045
6914
|
onPatch: (changes) => patchDimension(row.uid, changes)
|
|
@@ -6092,6 +6961,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6092
6961
|
}),
|
|
6093
6962
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
6094
6963
|
title: "Dicionário",
|
|
6964
|
+
collapseKey: "diagram:dictionary",
|
|
6095
6965
|
children: /* @__PURE__ */ jsx(DictionaryFields, {
|
|
6096
6966
|
row,
|
|
6097
6967
|
onPatch: (changes) => patchMeasure(row.uid, changes)
|
|
@@ -6118,6 +6988,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6118
6988
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6119
6989
|
/* @__PURE__ */ jsx(PropertyBlock, {
|
|
6120
6990
|
title: "Modelo",
|
|
6991
|
+
collapseKey: "diagram:model",
|
|
6121
6992
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
6122
6993
|
sx: {
|
|
6123
6994
|
fontSize: 12,
|
|
@@ -6128,6 +6999,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6128
6999
|
}),
|
|
6129
7000
|
/* @__PURE__ */ jsxs(PropertyBlock, {
|
|
6130
7001
|
title: "Relacionamentos",
|
|
7002
|
+
collapseKey: "diagram:relationships",
|
|
6131
7003
|
children: [!editor.relationships.length && /* @__PURE__ */ jsx(Typography, {
|
|
6132
7004
|
sx: {
|
|
6133
7005
|
fontSize: 12,
|
|
@@ -6196,6 +7068,7 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6196
7068
|
}),
|
|
6197
7069
|
/* @__PURE__ */ jsxs(PropertyBlock, {
|
|
6198
7070
|
title: "Medidas calculadas",
|
|
7071
|
+
collapseKey: "diagram:formulas",
|
|
6199
7072
|
children: [formulas.map((row) => /* @__PURE__ */ jsxs(Button, {
|
|
6200
7073
|
size: "small",
|
|
6201
7074
|
onClick: () => onSelect({
|
|
@@ -6213,22 +7086,10 @@ function Properties({ editor, selection, columns, errors, warnings, usage, onSel
|
|
|
6213
7086
|
]
|
|
6214
7087
|
}, row.uid)), /* @__PURE__ */ jsx(Button, {
|
|
6215
7088
|
size: "small",
|
|
6216
|
-
onClick: () => {
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
...current,
|
|
6221
|
-
measures: [...current.measures, emptyMeasure({
|
|
6222
|
-
uid,
|
|
6223
|
-
type: "formula",
|
|
6224
|
-
name: freeName("novaMedida", taken)
|
|
6225
|
-
})]
|
|
6226
|
-
}));
|
|
6227
|
-
onSelect({
|
|
6228
|
-
kind: "measure",
|
|
6229
|
-
uid
|
|
6230
|
-
});
|
|
6231
|
-
},
|
|
7089
|
+
onClick: () => onSelect({
|
|
7090
|
+
kind: "measure",
|
|
7091
|
+
uid: addFormulaTo(setEditor)
|
|
7092
|
+
}),
|
|
6232
7093
|
children: "+ Medida calculada"
|
|
6233
7094
|
})]
|
|
6234
7095
|
}),
|
|
@@ -6251,7 +7112,7 @@ function RenameField({ value, onCommit }) {
|
|
|
6251
7112
|
value: text,
|
|
6252
7113
|
onChange: setText,
|
|
6253
7114
|
onBlur: () => {
|
|
6254
|
-
if (
|
|
7115
|
+
if (IDENTIFIER.test(text)) onCommit(text);
|
|
6255
7116
|
else setText(value);
|
|
6256
7117
|
}
|
|
6257
7118
|
});
|
|
@@ -6429,11 +7290,22 @@ function ModelPanel({ devtools, original, capabilities, tables, onTry, onSaved }
|
|
|
6429
7290
|
});
|
|
6430
7291
|
const completions = useMemo(() => ({
|
|
6431
7292
|
tables,
|
|
6432
|
-
modelColumns: Object.fromEntries(editor.tables.map((row) => [row.name, columns?.tables[row.name] ?? []]))
|
|
7293
|
+
modelColumns: Object.fromEntries(editor.tables.map((row) => [row.name, columns?.tables[row.name] ?? []])),
|
|
7294
|
+
modelSql: Object.fromEntries(editor.tables.map((row) => [row.name, row.sql])),
|
|
7295
|
+
testSql: async (sql) => {
|
|
7296
|
+
const result = await fetcher({
|
|
7297
|
+
model: modelName,
|
|
7298
|
+
devtools: { testSql: sql }
|
|
7299
|
+
});
|
|
7300
|
+
if (!result.devtools?.sqlTest) throw new Error("O servidor não devolveu o teste do SQL");
|
|
7301
|
+
return result.devtools.sqlTest;
|
|
7302
|
+
}
|
|
6433
7303
|
}), [
|
|
6434
7304
|
tables,
|
|
6435
7305
|
columns,
|
|
6436
|
-
editor.tables
|
|
7306
|
+
editor.tables,
|
|
7307
|
+
fetcher,
|
|
7308
|
+
modelName
|
|
6437
7309
|
]);
|
|
6438
7310
|
const validationErrors = validation ? Object.entries(validation.errors) : [];
|
|
6439
7311
|
return /* @__PURE__ */ jsxs(SqlCompletionsContext.Provider, {
|
|
@@ -8582,4 +9454,4 @@ function ToggleButton$1({ draft, loading, problems, inspecting, onOpen }) {
|
|
|
8582
9454
|
//#endregion
|
|
8583
9455
|
export { DashboardDevtools as default };
|
|
8584
9456
|
|
|
8585
|
-
//# sourceMappingURL=DashboardDevtools-
|
|
9457
|
+
//# sourceMappingURL=DashboardDevtools-TFwjFdqn.js.map
|