@bluemarble/bi 0.8.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-wk4vMfhy.js → DashboardDevtools-TFwjFdqn.js} +139 -14
- package/dist/DashboardDevtools-TFwjFdqn.js.map +1 -0
- package/dist/{ModelDictionary-DZiYK1mV.js → ModelDictionary-g5d84D0n.js} +13 -4
- 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 +34 -7
- package/dist/client/index.js +184 -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-DXMHY9tU.d.ts → formatRules-C1PW2EiL.d.ts} +61 -1
- 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 +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +81 -81
- package/dist/DashboardDevtools-wk4vMfhy.js.map +0 -1
- package/dist/ModelDictionary-DZiYK1mV.js.map +0 -1
- package/dist/layout-BS3nO6OT.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
# bm-bi (`@bluemarble/bi`)
|
|
2
|
-
|
|
3
|
-
Camada semântica para dashboards em React, no lugar do Power BI: o modelo (tabelas,
|
|
4
|
-
relacionamentos, dimensões e medidas) é um objeto TS (`defineModel`), o servidor compila as
|
|
5
|
-
consultas para SQL (MySQL/MariaDB) e os componentes de filtro e gráfico consultam o modelo pelo
|
|
6
|
-
nome. Vem com um devtools (diagrama do modelo, editor de layout, playground, regressão).
|
|
7
|
-
|
|
8
|
-
```ts
|
|
9
|
-
import { defineModel, formula, sum } from '@bluemarble/bi/server'
|
|
10
|
-
|
|
11
|
-
export const vendas = defineModel({
|
|
12
|
-
name: 'vendas',
|
|
13
|
-
tables: {
|
|
14
|
-
pedidos: { key: 'id', sql: 'SELECT id, loja, criado_em FROM pedidos' },
|
|
15
|
-
itens: { sql: 'SELECT pedido_id, valor FROM itens' }
|
|
16
|
-
},
|
|
17
|
-
relationships: [{ from: 'itens.pedido_id', to: 'pedidos.id' }],
|
|
18
|
-
dimensions: { loja: { sql: 'pedidos.loja', type: 'string' } },
|
|
19
|
-
measures: { valor: sum('itens.valor', { format: 'currency' }) }
|
|
20
|
-
})
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
| Import | Onde roda |
|
|
24
|
-
| ----------------------- | -------------- |
|
|
25
|
-
| `@bluemarble/bi/server` | só no servidor |
|
|
26
|
-
| `@bluemarble/bi/client` | browser |
|
|
27
|
-
| `@bluemarble/bi/types` | ambos |
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm install @bluemarble/bi # peer: react 19, @mui/material 7, recharts 3
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Publicado no npm já compilado (`dist/`, ESM + `.d.ts`), sem precisar de `transpilePackages`.
|
|
34
|
-
Conceitos, API, devtools e configuração do app estão em
|
|
35
|
-
[`docs/dashboard-substituicao-powerbi.md`](docs/dashboard-substituicao-powerbi.md).
|
|
36
|
-
|
|
37
|
-
## Desenvolvimento
|
|
38
|
-
|
|
39
|
-
Projeto Vite+ com bun.
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
vp install # dependências
|
|
43
|
-
vp test # testes (vitest, sem banco)
|
|
44
|
-
vp run typecheck # tsc
|
|
45
|
-
vp check # formatação (oxfmt) + lint (oxlint)
|
|
46
|
-
vp run playground # dashboard + devtools em http://localhost:5180 (precisa do .env)
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Playground
|
|
50
|
-
|
|
51
|
-
`playground/` é uma página Vite com o dashboard e o devtools ligados direto ao `src/` (sem passar
|
|
52
|
-
pelo app). O endpoint `/api/dashboard/query` roda no próprio servidor do Vite e consulta o MySQL do
|
|
53
|
-
`DATABASE_URL` do `.env` na raiz (modelo em `.env.example`; o `.env` não é versionado).
|
|
54
|
-
|
|
55
|
-
- `playground/models/` — modelos (`defineModel`), registrados em `models/index.ts`;
|
|
56
|
-
- `playground/layouts/` — layouts JSON dos gráficos/filtros;
|
|
57
|
-
- `playground/dashboards.ts` — lista do seletor no topo (modelo + layout + filtros iniciais) e formatters.
|
|
58
|
-
|
|
59
|
-
O "Salvar no arquivo" do devtools grava nessas pastas (formatando com `vp fmt`) e os snapshots vão
|
|
60
|
-
para `playground/snapshots/`. Mudanças em modelos valem na consulta seguinte, sem reiniciar.
|
|
61
|
-
|
|
62
|
-
### Testar no app antes de publicar
|
|
63
|
-
|
|
64
|
-
Para usar a versão local num app (ex.: o `custo` do `rocha-pr`), aponte a dependência para esta
|
|
65
|
-
pasta (`"@bluemarble/bi": "file:../../../bm-bi"` + `npm install`) e deixe `vp run dev` rodando
|
|
66
|
-
(`vp pack --watch`): o app lê o `dist/`. Com o link, o Next precisa de `turbopack.root` e
|
|
67
|
-
`turbopack.resolveAlias` — o `next.config.js` do `custo` liga isso sozinho quando detecta o link.
|
|
68
|
-
|
|
69
|
-
## Publicar
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
vp run verify # o mesmo que o CI roda: check, typecheck, testes e build
|
|
73
|
-
vp run release # bumpp: escolhe a versão, commita, cria a tag vX.Y.Z e faz push
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
A tag dispara `.github/workflows/publish.yml`, que confere tag x `package.json`, roda o `verify` e
|
|
77
|
-
publica com `bun publish` (secret `NPM_TOKEN`). Depois, no app: `npm install @bluemarble/bi@X.Y.Z`.
|
|
1
|
+
# bm-bi (`@bluemarble/bi`)
|
|
2
|
+
|
|
3
|
+
Camada semântica para dashboards em React, no lugar do Power BI: o modelo (tabelas,
|
|
4
|
+
relacionamentos, dimensões e medidas) é um objeto TS (`defineModel`), o servidor compila as
|
|
5
|
+
consultas para SQL (MySQL/MariaDB) e os componentes de filtro e gráfico consultam o modelo pelo
|
|
6
|
+
nome. Vem com um devtools (diagrama do modelo, editor de layout, playground, regressão).
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { defineModel, formula, sum } from '@bluemarble/bi/server'
|
|
10
|
+
|
|
11
|
+
export const vendas = defineModel({
|
|
12
|
+
name: 'vendas',
|
|
13
|
+
tables: {
|
|
14
|
+
pedidos: { key: 'id', sql: 'SELECT id, loja, criado_em FROM pedidos' },
|
|
15
|
+
itens: { sql: 'SELECT pedido_id, valor FROM itens' }
|
|
16
|
+
},
|
|
17
|
+
relationships: [{ from: 'itens.pedido_id', to: 'pedidos.id' }],
|
|
18
|
+
dimensions: { loja: { sql: 'pedidos.loja', type: 'string' } },
|
|
19
|
+
measures: { valor: sum('itens.valor', { format: 'currency' }) }
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| Import | Onde roda |
|
|
24
|
+
| ----------------------- | -------------- |
|
|
25
|
+
| `@bluemarble/bi/server` | só no servidor |
|
|
26
|
+
| `@bluemarble/bi/client` | browser |
|
|
27
|
+
| `@bluemarble/bi/types` | ambos |
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @bluemarble/bi # peer: react 19, @mui/material 7, recharts 3
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Publicado no npm já compilado (`dist/`, ESM + `.d.ts`), sem precisar de `transpilePackages`.
|
|
34
|
+
Conceitos, API, devtools e configuração do app estão em
|
|
35
|
+
[`docs/dashboard-substituicao-powerbi.md`](docs/dashboard-substituicao-powerbi.md).
|
|
36
|
+
|
|
37
|
+
## Desenvolvimento
|
|
38
|
+
|
|
39
|
+
Projeto Vite+ com bun.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
vp install # dependências
|
|
43
|
+
vp test # testes (vitest, sem banco)
|
|
44
|
+
vp run typecheck # tsc
|
|
45
|
+
vp check # formatação (oxfmt) + lint (oxlint)
|
|
46
|
+
vp run playground # dashboard + devtools em http://localhost:5180 (precisa do .env)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Playground
|
|
50
|
+
|
|
51
|
+
`playground/` é uma página Vite com o dashboard e o devtools ligados direto ao `src/` (sem passar
|
|
52
|
+
pelo app). O endpoint `/api/dashboard/query` roda no próprio servidor do Vite e consulta o MySQL do
|
|
53
|
+
`DATABASE_URL` do `.env` na raiz (modelo em `.env.example`; o `.env` não é versionado).
|
|
54
|
+
|
|
55
|
+
- `playground/models/` — modelos (`defineModel`), registrados em `models/index.ts`;
|
|
56
|
+
- `playground/layouts/` — layouts JSON dos gráficos/filtros;
|
|
57
|
+
- `playground/dashboards.ts` — lista do seletor no topo (modelo + layout + filtros iniciais) e formatters.
|
|
58
|
+
|
|
59
|
+
O "Salvar no arquivo" do devtools grava nessas pastas (formatando com `vp fmt`) e os snapshots vão
|
|
60
|
+
para `playground/snapshots/`. Mudanças em modelos valem na consulta seguinte, sem reiniciar.
|
|
61
|
+
|
|
62
|
+
### Testar no app antes de publicar
|
|
63
|
+
|
|
64
|
+
Para usar a versão local num app (ex.: o `custo` do `rocha-pr`), aponte a dependência para esta
|
|
65
|
+
pasta (`"@bluemarble/bi": "file:../../../bm-bi"` + `npm install`) e deixe `vp run dev` rodando
|
|
66
|
+
(`vp pack --watch`): o app lê o `dist/`. Com o link, o Next precisa de `turbopack.root` e
|
|
67
|
+
`turbopack.resolveAlias` — o `next.config.js` do `custo` liga isso sozinho quando detecta o link.
|
|
68
|
+
|
|
69
|
+
## Publicar
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
vp run verify # o mesmo que o CI roda: check, typecheck, testes e build
|
|
73
|
+
vp run release # bumpp: escolhe a versão, commita, cria a tag vX.Y.Z e faz push
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A tag dispara `.github/workflows/publish.yml`, que confere tag x `package.json`, roda o `verify` e
|
|
77
|
+
publica com `bun publish` (secret `NPM_TOKEN`). Depois, no app: `npm install @bluemarble/bi@X.Y.Z`.
|
|
@@ -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 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-
|
|
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, {
|
|
@@ -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
|
/**
|
|
@@ -4060,7 +4185,7 @@ function Builder({ tables, sql, onClose, onApply }) {
|
|
|
4060
4185
|
] });
|
|
4061
4186
|
}
|
|
4062
4187
|
//#endregion
|
|
4063
|
-
//#region src/devtools/
|
|
4188
|
+
//#region src/devtools/queryTemplateDefs.ts
|
|
4064
4189
|
/**
|
|
4065
4190
|
* `WITH` da origem: a tabela do banco entra pelo nome; o SQL de uma tabela do modelo vira o CTE
|
|
4066
4191
|
* `origem`, antes dos CTEs do template.
|
|
@@ -9329,4 +9454,4 @@ function ToggleButton$1({ draft, loading, problems, inspecting, onOpen }) {
|
|
|
9329
9454
|
//#endregion
|
|
9330
9455
|
export { DashboardDevtools as default };
|
|
9331
9456
|
|
|
9332
|
-
//# sourceMappingURL=DashboardDevtools-
|
|
9457
|
+
//# sourceMappingURL=DashboardDevtools-TFwjFdqn.js.map
|