@bluemarble/bi 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DashboardDevtools-C4E9afPT.js → DashboardDevtools-7L93aV1-.js} +617 -230
- package/dist/DashboardDevtools-7L93aV1-.js.map +1 -0
- package/dist/{ModelDictionary-kUKcdbc8.js → ModelDictionary-Y7W9oK1J.js} +3 -3
- package/dist/{ModelDictionary-kUKcdbc8.js.map → ModelDictionary-Y7W9oK1J.js.map} +1 -1
- package/dist/client/index.d.ts +54 -17
- package/dist/client/index.js +121 -57
- package/dist/client/index.js.map +1 -1
- package/dist/{layout-DTR4vI-I.js → layout-676sEvvb.js} +84 -32
- package/dist/layout-676sEvvb.js.map +1 -0
- package/dist/server/index.d.ts +1 -5
- package/dist/server/index.js +1 -1
- package/dist/{types-DUJS2Zmx.d.ts → types-HXAn-3I1.d.ts} +47 -13
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/dist/DashboardDevtools-C4E9afPT.js.map +0 -1
- package/dist/layout-DTR4vI-I.js.map +0 -1
|
@@ -185,7 +185,7 @@ function validateModel(model) {
|
|
|
185
185
|
if ("base" in model || "joins" in model) return ["o formato `base` + `joins` não existe mais: use `tables` + `relationships`"];
|
|
186
186
|
if (!isRecord$1(model.tables)) return ["`tables` é obrigatório"];
|
|
187
187
|
if (model.relationships !== void 0 && !Array.isArray(model.relationships)) return ["`relationships` precisa ser uma lista"];
|
|
188
|
-
if (model.diagram !== void 0 && !diagramValid(model.diagram)) return ["`diagram` precisa ser { nome: { x, y } } com números"];
|
|
188
|
+
if (model.diagram !== void 0 && !diagramValid(model.diagram)) return ["`diagram` precisa ser { nome: { x, y, width?, height? } } com números (tamanho > 0)"];
|
|
189
189
|
const errors = [];
|
|
190
190
|
const { tables } = model;
|
|
191
191
|
const names = [...Object.keys(model.dimensions), ...Object.keys(model.measures)];
|
|
@@ -244,7 +244,7 @@ function validateModel(model) {
|
|
|
244
244
|
}
|
|
245
245
|
return errors;
|
|
246
246
|
}
|
|
247
|
-
const diagramValid = (diagram) => isRecord$1(diagram) && Object.values(diagram).every((point) => isRecord$1(point) && Number.isFinite(point.x) && Number.isFinite(point.y));
|
|
247
|
+
const diagramValid = (diagram) => isRecord$1(diagram) && Object.values(diagram).every((point) => isRecord$1(point) && Number.isFinite(point.x) && Number.isFinite(point.y) && [point.width, point.height].every((size) => size === void 0 || Number.isFinite(size) && size > 0));
|
|
248
248
|
function relationshipErrors(model, tableNames) {
|
|
249
249
|
const errors = [];
|
|
250
250
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -288,6 +288,7 @@ function relationshipErrors(model, tableNames) {
|
|
|
288
288
|
* Gera o código TS de um modelo (`defineModel({...})`). Usado pelo "Copiar TS" do devtools e pelo
|
|
289
289
|
* "Salvar no arquivo" do servidor, que troca só a chamada do `defineModel` dentro do arquivo.
|
|
290
290
|
*/
|
|
291
|
+
const diagramCode = ({ x, y, width, height }) => `{ x: ${x}, y: ${y}${width === void 0 ? "" : `, width: ${width}`}${height === void 0 ? "" : `, height: ${height}`} }`;
|
|
291
292
|
/** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */
|
|
292
293
|
function canonicalJson(value) {
|
|
293
294
|
return JSON.stringify(value, (_, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : item);
|
|
@@ -328,7 +329,7 @@ function modelObjectCode(model) {
|
|
|
328
329
|
`type: '${dim.type}'`,
|
|
329
330
|
...metaEntries(dim)
|
|
330
331
|
].join(", ").concat(" }"))}\n },`, ` measures: {\n${entries(model.measures, measureCode)}\n }${model.diagram ? "," : ""}`);
|
|
331
|
-
if (model.diagram) lines.push(` diagram: {\n${entries(model.diagram,
|
|
332
|
+
if (model.diagram) lines.push(` diagram: {\n${entries(model.diagram, diagramCode)}\n }`);
|
|
332
333
|
lines.push("}");
|
|
333
334
|
return lines.join("\n");
|
|
334
335
|
}
|
|
@@ -416,6 +417,31 @@ const hideEmpty = {
|
|
|
416
417
|
label: "Esconder grupos vazios",
|
|
417
418
|
section: "Dados"
|
|
418
419
|
};
|
|
420
|
+
/**
|
|
421
|
+
* Rótulo de cada dimensão/medida do gráfico. Um tipo do app ganha o mesmo editor com um campo
|
|
422
|
+
* `{ kind: 'labels' }` (o componente recebe `labels`; ver `fieldLabel`) e `{ kind: 'formats' }`.
|
|
423
|
+
*/
|
|
424
|
+
const LABELS_FIELD = {
|
|
425
|
+
kind: "labels",
|
|
426
|
+
label: "Rótulos",
|
|
427
|
+
hint: "Só neste gráfico; vazio = rótulo do dicionário do modelo",
|
|
428
|
+
section: "Rótulos e formatos"
|
|
429
|
+
};
|
|
430
|
+
/** Formato de cada medida do gráfico (o componente recebe `formats`; ver `measureFormatter`). */
|
|
431
|
+
const FORMATS_FIELD = {
|
|
432
|
+
kind: "formats",
|
|
433
|
+
label: "Formatos",
|
|
434
|
+
hint: "Só neste gráfico; vazio = formato do dicionário do modelo",
|
|
435
|
+
section: "Rótulos e formatos"
|
|
436
|
+
};
|
|
437
|
+
const percentDecimals = {
|
|
438
|
+
kind: "integer",
|
|
439
|
+
min: 0,
|
|
440
|
+
max: 6,
|
|
441
|
+
default: 1,
|
|
442
|
+
label: "Casas do %",
|
|
443
|
+
section: "Rótulos e formatos"
|
|
444
|
+
};
|
|
419
445
|
const CHART_FIELDS = {
|
|
420
446
|
kpi: {
|
|
421
447
|
title: {
|
|
@@ -436,12 +462,11 @@ const CHART_FIELDS = {
|
|
|
436
462
|
hint: "Linha menor embaixo do número (ex.: volume, custo/t)",
|
|
437
463
|
section: "Dados"
|
|
438
464
|
},
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
465
|
+
labels: {
|
|
466
|
+
...LABELS_FIELD,
|
|
467
|
+
hint: "O da medida principal aparece acima do número; o da secundária, na linha de baixo"
|
|
468
|
+
},
|
|
469
|
+
formats: FORMATS_FIELD
|
|
445
470
|
},
|
|
446
471
|
combo: {
|
|
447
472
|
...common(360),
|
|
@@ -480,21 +505,6 @@ const CHART_FIELDS = {
|
|
|
480
505
|
},
|
|
481
506
|
clickToFilter,
|
|
482
507
|
hideEmpty,
|
|
483
|
-
barLabel: {
|
|
484
|
-
kind: "text",
|
|
485
|
-
label: "Rótulo da barra",
|
|
486
|
-
section: "Aparência"
|
|
487
|
-
},
|
|
488
|
-
lineLabel: {
|
|
489
|
-
kind: "text",
|
|
490
|
-
label: "Rótulo da linha",
|
|
491
|
-
section: "Aparência"
|
|
492
|
-
},
|
|
493
|
-
detailLabel: {
|
|
494
|
-
kind: "text",
|
|
495
|
-
label: "Rótulo do detalhe",
|
|
496
|
-
section: "Aparência"
|
|
497
|
-
},
|
|
498
508
|
barColor: {
|
|
499
509
|
kind: "color",
|
|
500
510
|
default: "#F58220",
|
|
@@ -506,7 +516,9 @@ const CHART_FIELDS = {
|
|
|
506
516
|
default: "#555555",
|
|
507
517
|
label: "Cor da linha",
|
|
508
518
|
section: "Aparência"
|
|
509
|
-
}
|
|
519
|
+
},
|
|
520
|
+
labels: LABELS_FIELD,
|
|
521
|
+
formats: FORMATS_FIELD
|
|
510
522
|
},
|
|
511
523
|
bar: {
|
|
512
524
|
...common(360),
|
|
@@ -570,7 +582,9 @@ const CHART_FIELDS = {
|
|
|
570
582
|
default: true,
|
|
571
583
|
label: "Mostrar valores",
|
|
572
584
|
section: "Aparência"
|
|
573
|
-
}
|
|
585
|
+
},
|
|
586
|
+
labels: LABELS_FIELD,
|
|
587
|
+
formats: FORMATS_FIELD
|
|
574
588
|
},
|
|
575
589
|
donut: {
|
|
576
590
|
...common(300),
|
|
@@ -596,7 +610,10 @@ const CHART_FIELDS = {
|
|
|
596
610
|
hint: "As menores além dessa quantidade viram \"Outros\"",
|
|
597
611
|
section: "Dados"
|
|
598
612
|
},
|
|
599
|
-
clickToFilter
|
|
613
|
+
clickToFilter,
|
|
614
|
+
labels: LABELS_FIELD,
|
|
615
|
+
formats: FORMATS_FIELD,
|
|
616
|
+
percentDecimals
|
|
600
617
|
},
|
|
601
618
|
table: {
|
|
602
619
|
...common(400),
|
|
@@ -649,7 +666,10 @@ const CHART_FIELDS = {
|
|
|
649
666
|
clickToFilter: {
|
|
650
667
|
...clickToFilter,
|
|
651
668
|
hint: "Clicar numa linha filtra pela primeira dimensão"
|
|
652
|
-
}
|
|
669
|
+
},
|
|
670
|
+
labels: LABELS_FIELD,
|
|
671
|
+
formats: FORMATS_FIELD,
|
|
672
|
+
percentDecimals
|
|
653
673
|
}
|
|
654
674
|
};
|
|
655
675
|
const BUILTIN_CHART_TYPES = Object.fromEntries(CHART_TYPES.map((type) => [type, {
|
|
@@ -889,8 +909,34 @@ function fieldError(spec, value) {
|
|
|
889
909
|
if (spec.required && !value.length) return "precisa de pelo menos um item";
|
|
890
910
|
return new Set(value).size === value.length ? null : "tem item repetido";
|
|
891
911
|
case "formatters": return isRecord(value) && Object.values(value).every(isName) ? null : "precisa ser { dimensão: formatter }";
|
|
912
|
+
case "labels": return isRecord(value) && Object.values(value).every((label) => typeof label === "string") ? null : "precisa ser { coluna: rótulo }";
|
|
913
|
+
case "formats":
|
|
914
|
+
if (!isRecord(value)) return "precisa ser { medida: formato }";
|
|
915
|
+
for (const [measure, format] of Object.entries(value)) {
|
|
916
|
+
const error = numberFormatError(format);
|
|
917
|
+
if (error) return `"${measure}": ${error}`;
|
|
918
|
+
}
|
|
919
|
+
return null;
|
|
892
920
|
}
|
|
893
921
|
}
|
|
922
|
+
const NUMBER_FORMAT_KEYS = [
|
|
923
|
+
"format",
|
|
924
|
+
"decimals",
|
|
925
|
+
"compact",
|
|
926
|
+
"prefix",
|
|
927
|
+
"suffix"
|
|
928
|
+
];
|
|
929
|
+
function numberFormatError(format) {
|
|
930
|
+
if (!isRecord(format)) return "precisa ser um objeto";
|
|
931
|
+
const unknown = Object.keys(format).find((key) => !NUMBER_FORMAT_KEYS.includes(key));
|
|
932
|
+
if (unknown) return `campo desconhecido \`${unknown}\``;
|
|
933
|
+
const { decimals } = format;
|
|
934
|
+
if (format.format !== void 0 && !VALUE_FORMATS.includes(format.format)) return `formato inválido "${String(format.format)}"`;
|
|
935
|
+
if (decimals !== void 0 && (typeof decimals !== "number" || !Number.isInteger(decimals) || decimals < 0 || decimals > 10)) return "`decimals` precisa ser um inteiro de 0 a 10";
|
|
936
|
+
if (format.compact !== void 0 && typeof format.compact !== "boolean") return "`compact` precisa ser true/false";
|
|
937
|
+
for (const key of ["prefix", "suffix"]) if (format[key] !== void 0 && typeof format[key] !== "string") return `\`${key}\` precisa ser texto`;
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
894
940
|
/**
|
|
895
941
|
* Problemas de forma do layout (vazio = válido). Confere a forma do objeto porque o rascunho do
|
|
896
942
|
* devtools chega como JSON da request.
|
|
@@ -1007,6 +1053,9 @@ function layoutModelWarnings(layout, model, types = BUILTIN_CHART_TYPES) {
|
|
|
1007
1053
|
for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {
|
|
1008
1054
|
const value = chart[key];
|
|
1009
1055
|
if (spec.kind === "column" && isName(value) && !columns.includes(value)) warnings.push(`${name}: "${value}" (${key}) não é coluna do gráfico`);
|
|
1056
|
+
if ((spec.kind === "labels" || spec.kind === "formats") && isRecord(value)) {
|
|
1057
|
+
for (const column of Object.keys(value)) if (!columns.includes(column)) warnings.push(`${name}: "${column}" (${key}) não é coluna do gráfico`);
|
|
1058
|
+
}
|
|
1010
1059
|
}
|
|
1011
1060
|
}
|
|
1012
1061
|
for (const filter of layout.filters ?? []) if (!model.dimensions[filter.dimension]) warnings.push(`filtro "${filter.label || filter.dimension}": dimensão "${filter.dimension}" não existe`);
|
|
@@ -1017,18 +1066,21 @@ function layoutModelWarnings(layout, model, types = BUILTIN_CHART_TYPES) {
|
|
|
1017
1066
|
* escolhendo dimensão/medidas do modelo.
|
|
1018
1067
|
*/
|
|
1019
1068
|
function chartOfType(type, model, base, types = BUILTIN_CHART_TYPES) {
|
|
1020
|
-
const { id, title, span, card } = base;
|
|
1069
|
+
const { id, title, span, card, labels, formats } = base;
|
|
1021
1070
|
const previous = chartReferences(base, types);
|
|
1022
1071
|
const measures = Object.entries(model.measures);
|
|
1023
1072
|
const firstSum = measures.find(([, measure]) => isAggregate(measure))?.[0];
|
|
1024
1073
|
const firstRatio = measures.find(([, measure]) => measure.type === "ratio")?.[0];
|
|
1025
1074
|
const dimension = previous.dimensions[0] ?? Object.keys(model.dimensions)[0] ?? "";
|
|
1026
1075
|
const measure = previous.measures[0] ?? firstSum ?? measures[0]?.[0] ?? "";
|
|
1076
|
+
const fields = types[type]?.fields ?? {};
|
|
1027
1077
|
const shared = {
|
|
1028
1078
|
id,
|
|
1029
1079
|
title,
|
|
1030
1080
|
...span !== void 0 && { span },
|
|
1031
|
-
...card && { card }
|
|
1081
|
+
...card && { card },
|
|
1082
|
+
...labels && fields.labels?.kind === "labels" && { labels },
|
|
1083
|
+
...formats && fields.formats?.kind === "formats" && { formats }
|
|
1032
1084
|
};
|
|
1033
1085
|
switch (type) {
|
|
1034
1086
|
case "kpi": return {
|
|
@@ -1071,7 +1123,7 @@ function chartOfType(type, model, base, types = BUILTIN_CHART_TYPES) {
|
|
|
1071
1123
|
dimensions: 0,
|
|
1072
1124
|
measures: 0
|
|
1073
1125
|
};
|
|
1074
|
-
for (const [key, spec] of Object.entries(
|
|
1126
|
+
for (const [key, spec] of Object.entries(fields)) {
|
|
1075
1127
|
if (!spec.required || key === "title" || key === "span") continue;
|
|
1076
1128
|
switch (spec.kind) {
|
|
1077
1129
|
case "dimension":
|
|
@@ -1108,4 +1160,4 @@ function layoutFileContent(layout) {
|
|
|
1108
1160
|
//#endregion
|
|
1109
1161
|
export { modelEdges as A, VALUE_FORMATS as C, validateModel as D, modelMeta as E, reachFrom as M, referencedTables as N, joinPlan as O, AGGREGATION_LABEL as S, isAggregate as T, canonicalJson as _, FILTER_FIELDS as a, toModelCode as b, chartOfType as c, filterReferences as d, isBuiltinChart as f, validateLayout as g, resolveChartTypes as h, CARD_FIELDS as i, parseColumnRef as j, measureHome as k, chartReferences as l, layoutModelWarnings as m, CANVAS_PRESETS as n, chartCardStyle as o, layoutFileContent as p, CARDS_FIELDS as r, chartColumns as s, BUILTIN_CHART_TYPES as t, customChartProps as u, modelImports as v, aggregateSql as w, AGGREGATIONS as x, modelObjectCode as y };
|
|
1110
1162
|
|
|
1111
|
-
//# sourceMappingURL=layout-
|
|
1163
|
+
//# sourceMappingURL=layout-676sEvvb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-676sEvvb.js","names":["isRecord"],"sources":["../src/star.ts","../src/model.ts","../src/codegen.ts","../src/layout.ts"],"sourcesContent":["/**\n * Modelo estrela (`tables` + `relationships`): tabelas fato e dimensão ligadas por relacionamentos\n * N:1, como no modelo do Power BI. Aqui ficam as regras do grafo — quem alcança quem e por qual\n * caminho —, usadas pela validação, pelo compilador e pelo diagrama do devtools.\n *\n * O filtro \"desce\" do lado 1 para o lado N: uma medida da fato `fPesagem` enxerga a dimensão\n * `dOperacao` se existe um caminho `fPesagem -> ... -> dOperacao` seguindo os relacionamentos no\n * sentido N -> 1 (snowflake vale: `fPesagem -> dOperacao -> dNavio`).\n */\n\nimport type { ModelDef, RelationshipDef } from './model'\n\nexport type ColumnRef = { table: string; column: string }\n\nexport type Edge = { from: ColumnRef; to: ColumnRef; index: number; inner: boolean }\n\n/** `'tabela.coluna'` -> `{ table, column }` (nulo se não tiver esse formato). */\nexport function parseColumnRef(ref: unknown): ColumnRef | null {\n if (typeof ref !== 'string') return null\n const match = /^([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(ref.trim())\n return match ? { table: match[1], column: match[2] } : null\n}\n\n/** Relacionamentos válidos do modelo, como arestas N -> 1. */\nexport function modelEdges(model: Pick<ModelDef, 'relationships'>): Edge[] {\n return (model.relationships ?? []).flatMap((relationship: RelationshipDef, index) => {\n const from = parseColumnRef(relationship?.from)\n const to = parseColumnRef(relationship?.to)\n return from && to ? [{ from, to, index, inner: relationship.inner === true }] : []\n })\n}\n\nconst escapeRegex = (text: string) => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Tabelas citadas no SQL como `tabela.coluna`. O que está entre aspas simples é ignorado, para\n * `'a.b'` num literal não contar como referência.\n */\nexport function referencedTables(sql: string, tables: string[]): string[] {\n const code = sql.replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n return tables.filter((name) => new RegExp(`(?<![\\\\w.\\`])${escapeRegex(name)}\\\\.`).test(code))\n}\n\n/** Passo de um caminho: entra em `table` pela aresta `edge` (de `edge.from.table`). */\nexport type PathStep = { table: string; edge: Edge }\n\nexport type Reach = {\n /** Tabela -> caminho mais curto a partir da origem (vazio para a própria origem). */\n paths: Map<string, PathStep[]>\n /**\n * Tabelas alcançadas por mais de um caminho (de qualquer tamanho): o filtro ficaria ambíguo. É a\n * mesma regra do Power BI, que só deixa um caminho ativo entre duas tabelas.\n */\n ambiguous: string[]\n}\n\n/** Busca em largura a partir de `start`, seguindo os relacionamentos no sentido N -> 1. */\nexport function reachFrom(start: string, edges: Edge[]): Reach {\n const paths = new Map<string, PathStep[]>([[start, []]])\n let frontier = [start]\n while (frontier.length) {\n const next: string[] = []\n for (const table of frontier) {\n for (const edge of edges) {\n if (edge.from.table !== table || paths.has(edge.to.table)) continue\n paths.set(edge.to.table, [...(paths.get(table) ?? []), { table: edge.to.table, edge }])\n next.push(edge.to.table)\n }\n }\n frontier = next\n }\n // Cada aresta saindo de uma tabela alcançada é um caminho até o destino dela\n const incoming = new Map<string, number>()\n for (const edge of edges)\n if (paths.has(edge.from.table))\n incoming.set(edge.to.table, (incoming.get(edge.to.table) ?? 0) + 1)\n const ambiguous = [...incoming]\n .filter(([table, count]) => table !== start && count > 1)\n .map(([table]) => table)\n return { paths, ambiguous }\n}\n\n/**\n * Tabelas que precisam entrar na consulta, na ordem do JOIN, para que a partir de `home` se chegue\n * a todas as `targets`. Como cada tabela é alcançada por um caminho só (o modelo valida ambiguidade),\n * a união dos caminhos é uma árvore: cada tabela entra uma vez, ligada pela aresta de quem veio antes.\n */\nexport function joinPlan(home: string, targets: Iterable<string>, edges: Edge[]): PathStep[] {\n const { paths } = reachFrom(home, edges)\n const plan: PathStep[] = []\n const added = new Set([home])\n const include = (target: string) => {\n for (const step of paths.get(target) ?? []) {\n if (added.has(step.table)) continue\n added.add(step.table)\n plan.push(step)\n }\n }\n for (const target of targets) include(target)\n // Relacionamento `inner` entra sempre que o lado N está na consulta: ele filtra mesmo sem\n // dimensão pedida (repete até não entrar mais nada, porque cada tabela nova pode ter os seus)\n for (let grew = true; grew;) {\n grew = false\n for (const edge of edges)\n if (edge.inner && added.has(edge.from.table) && !added.has(edge.to.table)) {\n include(edge.to.table)\n grew = true\n }\n }\n // Pai antes do filho: ordena pela profundidade do caminho\n const depth = (table: string) => paths.get(table)?.length ?? 0\n return plan.sort((a, b) => depth(a.table) - depth(b.table))\n}\n\n/**\n * Tabela de origem de uma medida `sum`: entre as tabelas que o SQL cita, a que alcança todas as\n * outras (ex.: `fPesagem.peso * dProduto.fator` agrega em `fPesagem`). Nulo se nenhuma alcança.\n */\nexport function measureHome(tables: string[], edges: Edge[]): string | null {\n if (!tables.length) return null\n return (\n tables.find((candidate) => {\n const { paths } = reachFrom(candidate, edges)\n return tables.every((table) => paths.has(table))\n }) ?? null\n )\n}\n","import { measureHome, modelEdges, parseColumnRef, reachFrom, referencedTables } from './star'\n\n/**\n * Tipos e validação do modelo semântico. Fica fora de `server/` porque o devtools (client) também\n * valida o rascunho editado antes de mandá-lo pro servidor.\n */\n\n/**\n * Dicionário: nome de negócio e explicação de cada campo. Os componentes usam como padrão (rótulo,\n * formatação) e o `ModelDictionary` vira a documentação do modelo para quem usa o dashboard.\n */\nexport type FieldMeta = {\n label?: string\n description?: string\n}\n\nexport const VALUE_FORMATS = ['number', 'integer', 'currency', 'percent'] as const\nexport type ValueFormat = (typeof VALUE_FORMATS)[number]\n\nexport type MeasureMeta = FieldMeta & {\n /** `percent` espera a fração (0.25 = 25%). */\n format?: ValueFormat\n /** Casas decimais; sem isso, vale o padrão do componente. */\n decimals?: number\n}\n\nexport type DimensionDef = FieldMeta & {\n sql: string\n type: 'number' | 'string'\n}\n\n/**\n * Agregações sobre uma expressão SQL (`tabela.coluna`). Todas valem nos dois formatos; no estrela,\n * agregam no grão da tabela de origem, como a soma.\n */\nexport const AGGREGATIONS = ['sum', 'count', 'countDistinct', 'avg', 'min', 'max'] as const\nexport type Aggregation = (typeof AGGREGATIONS)[number]\n\nexport const AGGREGATION_LABEL: Record<Aggregation, string> = {\n sum: 'Soma',\n count: 'Contagem',\n countDistinct: 'Contagem distinta',\n avg: 'Média',\n min: 'Mínimo',\n max: 'Máximo'\n}\n\n/** `COUNT` conta os valores não nulos; para contar linhas, use uma coluna que nunca é nula. */\nexport type AggregateMeasure = MeasureMeta & { type: Aggregation; sql: string }\nexport type SumMeasure = AggregateMeasure & { type: 'sum' }\nexport type RatioMeasure = MeasureMeta & { type: 'ratio'; numerator: string; denominator: string }\n/**\n * Combinação de outras medidas, já agregadas: `{nome}` vira o valor da medida (como `[Medida]` no\n * DAX). É o jeito de juntar medidas de fatos diferentes: `COALESCE({custoMmo}, 0) + COALESCE({custoAppa}, 0)`.\n */\nexport type FormulaMeasure = MeasureMeta & { type: 'formula'; expression: string }\nexport type MeasureDef = AggregateMeasure | RatioMeasure | FormulaMeasure\n\nexport const isAggregate = (measure: { type: unknown }): measure is AggregateMeasure =>\n AGGREGATIONS.includes(measure.type as Aggregation)\n\n/** O SQL agregado da medida: `SUM(x)`, `COUNT(DISTINCT x)`, `AVG(x)`... */\nexport function aggregateSql(measure: Pick<AggregateMeasure, 'type' | 'sql'>): string {\n if (measure.type === 'countDistinct') return `COUNT(DISTINCT ${measure.sql})`\n const fn = { sum: 'SUM', count: 'COUNT', avg: 'AVG', min: 'MIN', max: 'MAX' }[measure.type]\n return `${fn}(${measure.sql})`\n}\n\n/**\n * Tabela do modelo: o SQL é a \"consulta de origem\" (tabela, view ou SELECT). Não há tipo fato ou\n * dimensão: o papel sai dos relacionamentos (quem está no lado 1 filtra quem está no lado N) e de\n * onde ficam as medidas.\n */\nexport type TableDef = FieldMeta & {\n sql: string\n /** Coluna única da tabela; o diagrama usa ela para decidir o lado 1 ao criar relacionamentos. */\n key?: string\n}\n\n/** Relacionamento N:1 — o filtro do lado 1 (`to`) chega às linhas do lado N (`from`). */\nexport type RelationshipDef = {\n /** `tabela.coluna` do lado N (os lançamentos). */\n from: string\n /** `tabela.coluna` do lado 1 (a chave do cadastro). */\n to: string\n /**\n * \"Pressupor integridade referencial\" do Power BI: INNER JOIN em vez de LEFT JOIN, e a tabela do\n * lado 1 entra sempre na consulta da fato. Linhas do lado N sem par no lado 1 ficam de fora (em\n * vez de virar um grupo \"em branco\") — é o jeito de um filtro fixo da dimensão valer para a fato.\n */\n inner?: boolean\n}\n\n/**\n * Modelo estrela: `tables` (fato/dimensão) + `relationships`. Dimensões e medidas citam as colunas\n * como `tabela.coluna`; cada medida de agregação agrega no grão da própria tabela e chega às\n * dimensões seguindo os relacionamentos.\n */\nexport type ModelDef = {\n name: string\n tables: Record<string, TableDef>\n relationships?: RelationshipDef[]\n dimensions: Record<string, DimensionDef>\n measures: Record<string, MeasureDef>\n /**\n * Posição de cada card no diagrama do devtools (tabela -> canto superior esquerdo) e, se foi\n * redimensionado, o tamanho. Só visual: não muda nenhuma consulta. Card sem posição entra no\n * layout automático; sem tamanho, tem a largura padrão e a altura do conteúdo.\n */\n diagram?: Record<string, DiagramPosition>\n}\n\nexport type DiagramPosition = { x: number; y: number; width?: number; height?: number }\n\n/** Nomes citados como `{nome}` numa fórmula. */\nexport const formulaRefs = (expression: string) => [\n ...new Set([...expression.matchAll(/\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g)].map((match) => match[1]))\n]\n\n/** Medidas que uma medida usa diretamente (razão e fórmula). */\nexport function measureDeps(measure: MeasureDef): string[] {\n if (measure.type === 'ratio') return [measure.numerator, measure.denominator]\n if (measure.type === 'formula') return formulaRefs(measure.expression)\n return []\n}\n\nconst IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isText = (value: unknown): value is string => typeof value === 'string' && !!value.trim()\n\nfunction metaErrors(context: string, field: Record<string, unknown>): string[] {\n const errors: string[] = []\n for (const key of ['label', 'description'] as const) {\n if (field[key] !== undefined && typeof field[key] !== 'string')\n errors.push(`${context}: \\`${key}\\` precisa ser texto`)\n }\n if (field.format !== undefined && !VALUE_FORMATS.includes(field.format as ValueFormat))\n errors.push(`${context}: formato inválido \"${field.format}\"`)\n const { decimals } = field\n if (\n decimals !== undefined &&\n (typeof decimals !== 'number' || !Number.isInteger(decimals) || decimals < 0 || decimals > 10)\n )\n errors.push(`${context}: \\`decimals\\` precisa ser um inteiro entre 0 e 10`)\n return errors\n}\n\n/** Só os metadados (sem SQL): é o que o client recebe fora do devtools. */\nexport type ModelMeta = {\n name: string\n dimensions: Record<string, FieldMeta & { type: DimensionDef['type'] }>\n measures: Record<\n string,\n MeasureMeta & {\n type: MeasureDef['type']\n numerator?: string\n denominator?: string\n /** Medidas que a fórmula combina (a expressão em si fica no servidor). */\n refs?: string[]\n }\n >\n}\n\nexport function modelMeta(model: ModelDef): ModelMeta {\n const pick = <T extends FieldMeta & MeasureMeta>(field: T) => ({\n ...(field.label !== undefined && { label: field.label }),\n ...(field.description !== undefined && { description: field.description }),\n ...(field.format !== undefined && { format: field.format }),\n ...(field.decimals !== undefined && { decimals: field.decimals })\n })\n return {\n name: model.name,\n dimensions: Object.fromEntries(\n Object.entries(model.dimensions).map(([name, dim]) => [\n name,\n { type: dim.type, ...pick(dim) }\n ])\n ),\n measures: Object.fromEntries(\n Object.entries(model.measures).map(([name, measure]) => [\n name,\n {\n type: measure.type,\n // Contagem sem formato: inteiro (sem casas decimais)\n ...((measure.type === 'count' || measure.type === 'countDistinct') && {\n format: 'integer' as const\n }),\n ...pick(measure),\n ...(measure.type === 'ratio' && {\n numerator: measure.numerator,\n denominator: measure.denominator\n }),\n ...(measure.type === 'formula' && { refs: formulaRefs(measure.expression) })\n }\n ])\n )\n }\n}\n\n/**\n * Lista os problemas do modelo (vazio = válido). Confere também a forma do objeto, porque o rascunho\n * do devtools chega como JSON da request.\n */\nexport function validateModel(model: ModelDef): string[] {\n if (!isRecord(model)) return ['modelo inválido']\n if (!isRecord(model.dimensions) || !isRecord(model.measures))\n return ['`dimensions` e `measures` são obrigatórios']\n // Rascunho/arquivo antigo: o formato base + joins foi removido\n if ('base' in model || 'joins' in model)\n return ['o formato `base` + `joins` não existe mais: use `tables` + `relationships`']\n if (!isRecord(model.tables)) return ['`tables` é obrigatório']\n if (model.relationships !== undefined && !Array.isArray(model.relationships))\n return ['`relationships` precisa ser uma lista']\n\n if (model.diagram !== undefined && !diagramValid(model.diagram))\n return ['`diagram` precisa ser { nome: { x, y, width?, height? } } com números (tamanho > 0)']\n\n const errors: string[] = []\n const { tables } = model\n const names = [...Object.keys(model.dimensions), ...Object.keys(model.measures)]\n\n for (const name of [...names, ...Object.keys(tables)]) {\n if (!IDENTIFIER.test(name)) errors.push(`nome inválido: \"${name}\"`)\n else if (name.startsWith('__')) errors.push(`nome reservado: \"${name}\" (começa com \"__\")`)\n }\n\n const duplicated = names.find((name, index) => names.indexOf(name) !== index)\n if (duplicated) errors.push(`nome repetido: \"${duplicated}\"`)\n\n for (const [name, table] of Object.entries(tables)) {\n if (!isRecord(table) || !isText(table.sql)) {\n errors.push(`tabela \"${name}\" sem SQL`)\n continue\n }\n if (table.key !== undefined && (typeof table.key !== 'string' || !IDENTIFIER.test(table.key)))\n errors.push(`tabela \"${name}\": key inválida`)\n errors.push(...metaErrors(`tabela \"${name}\"`, table))\n }\n\n const tableNames = Object.keys(tables)\n errors.push(...relationshipErrors(model, tableNames))\n\n for (const [name, dimension] of Object.entries(model.dimensions)) {\n if (!isRecord(dimension) || !isText(dimension.sql)) errors.push(`dimensão \"${name}\" sem SQL`)\n else if (dimension.type !== 'number' && dimension.type !== 'string')\n errors.push(`dimensão \"${name}\" com tipo inválido`)\n else {\n errors.push(...metaErrors(`dimensão \"${name}\"`, dimension))\n if (referencedTables(dimension.sql, tableNames).length !== 1)\n errors.push(`dimensão \"${name}\" precisa usar colunas de uma tabela só (\\`tabela.coluna\\`)`)\n }\n }\n\n const edges = modelEdges(model)\n for (const [name, measure] of Object.entries(model.measures)) {\n if (!isRecord(measure)) {\n errors.push(`medida \"${name}\" inválida`)\n } else if (isAggregate(measure)) {\n if (!isText(measure.sql)) errors.push(`medida \"${name}\" sem SQL`)\n else {\n const used = referencedTables(measure.sql, tableNames)\n if (!used.length)\n errors.push(`medida \"${name}\" precisa usar colunas de uma tabela (\\`tabela.coluna\\`)`)\n else if (!measureHome(used, edges))\n errors.push(\n `medida \"${name}\" usa ${used.join(', ')}, mas nenhuma delas chega às outras pelos relacionamentos`\n )\n }\n } else if (measure.type === 'ratio' || measure.type === 'formula') {\n if (measure.type === 'formula' && !isText(measure.expression))\n errors.push(`medida \"${name}\" sem expressão`)\n else if (measure.type === 'formula' && !formulaRefs(measure.expression).length)\n errors.push(`medida \"${name}\": a fórmula precisa citar medidas como {nome}`)\n for (const ref of measureDeps(measure)) {\n if (typeof ref !== 'string' || !model.measures[ref])\n errors.push(`medida \"${name}\" referencia \"${ref}\", que não existe`)\n }\n } else {\n errors.push(`medida \"${name}\" com tipo inválido`)\n }\n if (isRecord(measure)) errors.push(...metaErrors(`medida \"${name}\"`, measure))\n }\n if (errors.length) return errors\n\n // Medidas circulares (a -> b -> a) só estourariam na hora de compilar a consulta\n const visit = (name: string, stack: string[]): string | null => {\n if (stack.includes(name)) return [...stack, name].join(' -> ')\n for (const dep of measureDeps(model.measures[name])) {\n const cycle = visit(dep, [...stack, name])\n if (cycle) return cycle\n }\n return null\n }\n for (const name of Object.keys(model.measures)) {\n const cycle = visit(name, [])\n if (cycle) {\n errors.push(`medida circular: ${cycle}`)\n break\n }\n }\n\n return errors\n}\n\nconst diagramValid = (diagram: unknown) =>\n isRecord(diagram) &&\n Object.values(diagram).every(\n (point) =>\n isRecord(point) &&\n Number.isFinite(point.x) &&\n Number.isFinite(point.y) &&\n [point.width, point.height].every(\n (size) => size === undefined || (Number.isFinite(size) && (size as number) > 0)\n )\n )\n\nfunction relationshipErrors(model: ModelDef, tableNames: string[]): string[] {\n const errors: string[] = []\n const seen = new Set<string>()\n for (const [index, relationship] of (model.relationships ?? []).entries()) {\n const context = `relacionamento ${index + 1}`\n if (!isRecord(relationship)) {\n errors.push(`${context} inválido`)\n continue\n }\n const from = parseColumnRef(relationship.from)\n const to = parseColumnRef(relationship.to)\n if (!from || !to) {\n errors.push(`${context}: use \\`tabela.coluna\\` em \\`from\\` e \\`to\\``)\n continue\n }\n for (const ref of [from, to])\n if (!tableNames.includes(ref.table))\n errors.push(`${context}: tabela \"${ref.table}\" não existe`)\n if (from.table === to.table)\n errors.push(`${context}: liga a tabela \"${from.table}\" a ela mesma`)\n if (relationship.inner !== undefined && typeof relationship.inner !== 'boolean')\n errors.push(`${context}: \\`inner\\` precisa ser true/false`)\n const pair = `${from.table}->${to.table}`\n if (seen.has(pair))\n errors.push(`${context}: já existe um relacionamento de \"${from.table}\" para \"${to.table}\"`)\n seen.add(pair)\n }\n if (errors.length) return errors\n\n // Dois caminhos até a mesma tabela deixariam o filtro ambíguo (o Power BI deixa um inativo)\n const edges = modelEdges(model)\n for (const table of tableNames) {\n const { ambiguous, paths } = reachFrom(table, edges)\n if (ambiguous.length) {\n errors.push(\n `caminho ambíguo de \"${table}\" até \"${ambiguous[0]}\": há mais de um relacionamento levando lá`\n )\n break\n }\n // Ciclo: a tabela alcança a si mesma\n if (edges.some((edge) => edge.to.table === table && paths.has(edge.from.table))) {\n errors.push(`relacionamentos em ciclo passando por \"${table}\"`)\n break\n }\n }\n return errors\n}\n","import {\n type DiagramPosition,\n type FieldMeta,\n isAggregate,\n type MeasureDef,\n type MeasureMeta,\n type ModelDef,\n type TableDef\n} from './model'\n\n/**\n * Gera o código TS de um modelo (`defineModel({...})`). Usado pelo \"Copiar TS\" do devtools e pelo\n * \"Salvar no arquivo\" do servidor, que troca só a chamada do `defineModel` dentro do arquivo.\n */\n\nconst diagramCode = ({ x, y, width, height }: DiagramPosition) =>\n `{ x: ${x}, y: ${y}${width === undefined ? '' : `, width: ${width}`}${height === undefined ? '' : `, height: ${height}`} }`\n\n/** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_, item) =>\n item && typeof item === 'object' && !Array.isArray(item)\n ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))\n : item\n )\n}\n\nexport function literal(text: string) {\n if (text.includes('\\n'))\n return `\\`${text.replaceAll('\\\\', '\\\\\\\\').replaceAll('`', '\\\\`').replaceAll('${', '\\\\${')}\\``\n return `'${text.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"\\\\'\")}'`\n}\n\nfunction metaEntries(field: FieldMeta & MeasureMeta) {\n const entries: string[] = []\n if (field.label !== undefined) entries.push(`label: ${literal(field.label)}`)\n if (field.description !== undefined) entries.push(`description: ${literal(field.description)}`)\n if (field.format !== undefined) entries.push(`format: '${field.format}'`)\n if (field.decimals !== undefined) entries.push(`decimals: ${field.decimals}`)\n return entries\n}\n\nfunction measureCode(measure: MeasureDef) {\n const meta = metaEntries(measure)\n const args = isAggregate(measure)\n ? [literal(measure.sql)]\n : measure.type === 'formula'\n ? [literal(measure.expression)]\n : [literal(measure.numerator), literal(measure.denominator)]\n if (meta.length) args.push(`{ ${meta.join(', ')} }`)\n return `${measure.type}(${args.join(', ')})`\n}\n\nfunction tableCode(table: TableDef) {\n const fields = [\n ...(table.key !== undefined ? [`key: ${literal(table.key)}`] : []),\n `sql: ${literal(table.sql)}`,\n ...metaEntries(table)\n ]\n return `{ ${fields.join(', ')} }`\n}\n\nconst entries = <T>(record: Record<string, T>, render: (value: T) => string) =>\n Object.entries(record)\n .map(([name, value]) => ` ${name}: ${render(value)}`)\n .join(',\\n')\n\n/** O objeto passado ao `defineModel`, começando em `{` (sem indentação na primeira linha). */\nexport function modelObjectCode(model: ModelDef): string {\n const lines = ['{', ` name: ${literal(model.name)},`]\n lines.push(\n Object.keys(model.tables).length\n ? ` tables: {\\n${entries(model.tables, tableCode)}\\n },`\n : ' tables: {},'\n )\n if (model.relationships?.length)\n lines.push(\n ` relationships: [\\n${model.relationships\n .map(\n (relationship) =>\n ` { from: ${literal(relationship.from)}, to: ${literal(relationship.to)}${relationship.inner ? ', inner: true' : ''} }`\n )\n .join(',\\n')}\\n ],`\n )\n lines.push(\n ` dimensions: {\\n${entries(model.dimensions, (dim) =>\n [`{ sql: ${literal(dim.sql)}`, `type: '${dim.type}'`, ...metaEntries(dim)]\n .join(', ')\n .concat(' }')\n )}\\n },`,\n ` measures: {\\n${entries(model.measures, measureCode)}\\n }${model.diagram ? ',' : ''}`\n )\n if (model.diagram) lines.push(` diagram: {\\n${entries(model.diagram, diagramCode)}\\n }`)\n lines.push('}')\n return lines.join('\\n')\n}\n\n/** Helpers do `@bluemarble/bi/server` que o código do modelo usa. */\nexport function modelImports(model: ModelDef) {\n // O helper tem o nome do tipo (`sum(...)`, `countDistinct(...)`, `formula(...)`)\n const types = new Set(Object.values(model.measures).map((measure) => measure.type))\n return ['defineModel', ...types].sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }))\n}\n\n/** Arquivo completo, pronto para colar em `src/dashboard/models/<nome>.ts`. */\nexport function toModelCode(model: ModelDef): string {\n return [\n `import { ${modelImports(model).join(', ')} } from '@bluemarble/bi/server'`,\n '',\n `export const ${model.name} = defineModel(${modelObjectCode(model)})`,\n ''\n ].join('\\n')\n}\n","/**\n * Layout de gráficos de uma página: lista declarativa (JSON) que o `<DashboardCharts>` renderiza. É\n * JSON — e não JSX — para o devtools conseguir adicionar e editar gráficos pelo navegador e gravar o\n * arquivo de volta. Fica fora de `server/` porque o client e o servidor validam o mesmo formato.\n *\n * Os campos de cada tipo são descritos em `CHART_FIELDS`: é de lá que saem a validação, os avisos\n * de dimensão/medida inexistente e o formulário da aba Layout do devtools. O app registra tipos\n * próprios (`ChartTypeSpec`) no `<DashboardProvider charts>` e no `devtools.chartTypes` do servidor;\n * as funções daqui recebem o registro completo (`resolveChartTypes`).\n */\n\nimport { isAggregate, type ModelDef, VALUE_FORMATS, type ValueFormat } from './model'\n\nexport const CHART_TYPES = ['kpi', 'combo', 'bar', 'donut', 'table'] as const\nexport type ChartType = (typeof CHART_TYPES)[number]\n\nexport const CHART_TYPE_LABEL: Record<ChartType, string> = {\n kpi: 'KPI (card)',\n combo: 'Combo (barras + linha)',\n bar: 'Barras',\n donut: 'Rosca',\n table: 'Tabela'\n}\n\n/** Colunas do grid em telas largas; cada gráfico ocupa `span` delas (no celular, a linha toda). */\nexport const LAYOUT_COLUMNS = 12\n\ntype BaseChartDef = {\n /** Identificador estável do gráfico dentro do layout (letras, números, \"-\" e \"_\"). */\n id: string\n title: string\n /** Colunas ocupadas (1 a 12). Padrão: 12. */\n span?: number\n height?: number\n /** Aparência do card deste gráfico (sobrepõe o `cards` do layout). */\n card?: CardStyle\n}\n\n/**\n * Formato de uma medida num gráfico: sobrepõe o `format`/`decimals` do dicionário do modelo só\n * naquele gráfico.\n */\nexport type ChartNumberFormat = {\n format?: ValueFormat\n /** Casas decimais (0 a 10). */\n decimals?: number\n /** Notação curta: 1,2 mil / 3,4 mi. */\n compact?: boolean\n /** Texto antes do número (ex.: \"US$ \"). */\n prefix?: string\n /** Texto depois do número (ex.: \" t\"). */\n suffix?: string\n}\n\n/** Rótulos e formatos do gráfico, por nome de dimensão/medida (sobrepõem o dicionário). */\ntype ColumnsDisplay = {\n /** `{ dimensãoOuMedida: 'Rótulo' }`: legenda, tooltip e cabeçalho. */\n labels?: Record<string, string>\n /** `{ medida: { decimals: 1, ... } }`. */\n formats?: Record<string, ChartNumberFormat>\n}\n\nexport type ComboChartDef = BaseChartDef &\n ColumnsDisplay & {\n type: 'combo'\n dimension: string\n bar: string\n line: string\n detail?: string\n /** Nome de um formatter passado ao `<DashboardCharts formatters>` (funções não cabem no JSON). */\n formatDimension?: string\n clickToFilter?: boolean\n hideEmpty?: boolean\n maxLabels?: number\n barColor?: string\n lineColor?: string\n }\n\nexport type BarChartDef = BaseChartDef &\n ColumnsDisplay & {\n type: 'bar'\n dimension: string\n /** Uma série por medida. */\n measures: string[]\n orientation?: 'vertical' | 'horizontal'\n stacked?: boolean\n /** `dimension` = ordem da dimensão; `desc`/`asc` = pela primeira medida. */\n sort?: 'dimension' | 'desc' | 'asc'\n /** Só os N primeiros depois de ordenar. */\n limit?: number\n formatDimension?: string\n clickToFilter?: boolean\n hideEmpty?: boolean\n showValues?: boolean\n }\n\nexport type DonutChartDef = BaseChartDef &\n ColumnsDisplay & {\n type: 'donut'\n dimension: string\n measure: string\n /** Fatias além dessa quantidade viram \"Outros\". */\n maxSlices?: number\n /** Casas decimais do percentual. Padrão: 1. */\n percentDecimals?: number\n formatDimension?: string\n clickToFilter?: boolean\n }\n\nexport type TableChartDef = BaseChartDef &\n ColumnsDisplay & {\n type: 'table'\n dimensions?: string[]\n measures: string[]\n /**\n * Medidas com coluna de participação no total: valor da linha ÷ soma da coluna. Medida que não\n * está em `measures` é consultada só para o percentual.\n */\n percentOf?: string[]\n /** Casas decimais das colunas de percentual. Padrão: 1. */\n percentDecimals?: number\n /** Formatter por dimensão (`{ mes: 'nomeMes' }`). */\n formatDimensions?: Record<string, string>\n /** Linha de total, consultada sem as dimensões (razões saem certas, não somadas). */\n showTotal?: boolean\n /** Coluna (dimensão ou medida) da ordem inicial; o usuário reordena clicando no cabeçalho. */\n sortBy?: string\n sortDirection?: 'asc' | 'desc'\n clickToFilter?: boolean\n }\n\nexport type KpiChartDef = Omit<BaseChartDef, 'height'> &\n ColumnsDisplay & {\n type: 'kpi'\n /** Medida do número grande (total com os filtros da página). */\n measure: string\n /** Medida extra numa linha menor embaixo (ex.: volume, custo/t). */\n secondary?: string\n }\n\n/** Gráficos da lib (`type` em `CHART_TYPES`). */\nexport type BuiltinChartDef =\n | KpiChartDef\n | ComboChartDef\n | BarChartDef\n | DonutChartDef\n | TableChartDef\n\n/** Gráfico de um tipo registrado pelo app: os campos vêm do `ChartTypeSpec` dele. */\nexport type CustomChartDef = BaseChartDef & { type: string; [field: string]: unknown }\n\nexport type ChartDef = BuiltinChartDef | CustomChartDef\n\nexport const isBuiltinChart = (chart: ChartDef): chart is BuiltinChartDef =>\n (CHART_TYPES as readonly string[]).includes(chart.type)\n\n/** Filtro (slicer) da página: múltipla escolha de uma dimensão, com filtro cruzado. */\nexport type FilterDef = {\n dimension: string\n /** Padrão: `label` da dimensão no dicionário. */\n label?: string\n width?: number\n /** Nome de um formatter do `<DashboardFilters formatters>` para as opções. */\n formatLabel?: string\n}\n\n/**\n * Tela de tamanho fixo: o `<DashboardCanvas>` desenha o dashboard nessas dimensões e o escala para\n * caber na largura da tela, mantendo a proporção — os gráficos não se reorganizam (como o \"Ajustar à\n * largura\" do Power BI).\n */\nexport type CanvasDef = {\n /** Largura de projeto, em px (ex.: 1920). */\n width: number\n /** Altura mínima de projeto, em px (ex.: 1080); conteúdo mais alto estende a tela. */\n height?: number\n}\n\n/** Moldura de um gráfico (o card): ausente = padrão do componente. */\nexport type CardStyle = {\n background?: string\n borderColor?: string\n /** Espessura da borda, em px (0 = sem borda). */\n borderWidth?: number\n /** Arredondamento dos cantos, em px. */\n borderRadius?: number\n /** Sombra (elevation do MUI, 0 a 24). */\n shadow?: number\n /** Espaço interno, em px. */\n padding?: number\n titleColor?: string\n /** Tamanho do título, em px. */\n titleSize?: number\n titleAlign?: 'left' | 'center' | 'right'\n}\n\n/** Cards de todos os gráficos do layout: estilo padrão e espaço entre eles. */\nexport type CardsStyle = CardStyle & {\n /** Espaço entre os cards, em px. */\n gap?: number\n}\n\nexport type ChartsLayout = {\n /** Nome do arquivo (`<layoutsDir>/<id>.json`) e chave do rascunho no devtools. */\n id: string\n /** Ausente = responsivo (12 colunas em telas largas, um gráfico por linha no celular). */\n canvas?: CanvasDef\n /** Estilo padrão dos cards e espaço entre eles; cada gráfico sobrepõe com `card`. */\n cards?: CardsStyle\n /** Renderizados pelo `<DashboardFilters>`, na ordem. */\n filters?: FilterDef[]\n charts: ChartDef[]\n}\n\nexport const LAYOUT_ID = /^[A-Za-z0-9_-]{1,80}$/\n\n// --- campos por tipo ---\n\nexport type FieldSpec = {\n label: string\n hint?: string\n /** Agrupamento no formulário do devtools. */\n section: 'Gráfico' | 'Dados' | 'Aparência' | 'Rótulos e formatos' | 'Filtro' | 'Tela' | 'Card'\n required?: boolean\n /** Valor que o componente usa quando o campo não está no JSON. */\n default?: unknown\n} & (\n | { kind: 'text' }\n | { kind: 'color' }\n | { kind: 'boolean' }\n | { kind: 'integer'; min: number; max: number }\n | { kind: 'enum'; values: readonly string[]; labels?: Record<string, string> }\n /** Nome de dimensão/medida do modelo (uma ou uma lista). */\n | { kind: 'dimension' | 'measure' }\n | { kind: 'dimensions' | 'measures' }\n /** Nome de um formatter do `<DashboardCharts formatters>`. */\n | { kind: 'formatter' }\n /** `{ dimensão: formatter }`. */\n | { kind: 'formatters' }\n /** Uma das dimensões/medidas escolhidas no próprio gráfico. */\n | { kind: 'column' }\n /** `{ dimensãoOuMedida: rótulo }` das colunas do gráfico. */\n | { kind: 'labels' }\n /** `{ medida: ChartNumberFormat }` das medidas do gráfico. */\n | { kind: 'formats' }\n)\n\nconst common = (height: number): Record<string, FieldSpec> => ({\n title: { kind: 'text', label: 'Título', section: 'Gráfico' },\n span: {\n kind: 'integer',\n min: 1,\n max: LAYOUT_COLUMNS,\n default: LAYOUT_COLUMNS,\n label: 'Largura',\n hint: 'Colunas ocupadas em telas largas; no celular, cada gráfico ocupa a linha toda (menos com a tela de tamanho fixo)',\n section: 'Gráfico'\n },\n height: {\n kind: 'integer',\n min: 120,\n max: 2000,\n default: height,\n label: 'Altura (px)',\n section: 'Gráfico'\n }\n})\n\nconst formatDimension: FieldSpec = {\n kind: 'formatter',\n label: 'Formatar dimensão',\n hint: 'Formatters passados ao <DashboardCharts formatters={{ ... }}> da página',\n section: 'Dados'\n}\nconst clickToFilter: FieldSpec = {\n kind: 'boolean',\n default: false,\n label: 'Clique filtra o dashboard',\n hint: 'Ctrl/Cmd+clique acumula; clicar de novo no único selecionado limpa',\n section: 'Dados'\n}\nconst hideEmpty: FieldSpec = {\n kind: 'boolean',\n default: true,\n label: 'Esconder grupos vazios',\n section: 'Dados'\n}\n\n/**\n * Rótulo de cada dimensão/medida do gráfico. Um tipo do app ganha o mesmo editor com um campo\n * `{ kind: 'labels' }` (o componente recebe `labels`; ver `fieldLabel`) e `{ kind: 'formats' }`.\n */\nconst LABELS_FIELD: FieldSpec = {\n kind: 'labels',\n label: 'Rótulos',\n hint: 'Só neste gráfico; vazio = rótulo do dicionário do modelo',\n section: 'Rótulos e formatos'\n}\n/** Formato de cada medida do gráfico (o componente recebe `formats`; ver `measureFormatter`). */\nconst FORMATS_FIELD: FieldSpec = {\n kind: 'formats',\n label: 'Formatos',\n hint: 'Só neste gráfico; vazio = formato do dicionário do modelo',\n section: 'Rótulos e formatos'\n}\nconst percentDecimals: FieldSpec = {\n kind: 'integer',\n min: 0,\n max: 6,\n default: 1,\n label: 'Casas do %',\n section: 'Rótulos e formatos'\n}\n\nexport const CHART_FIELDS: Record<ChartType, Record<string, FieldSpec>> = {\n kpi: {\n title: { kind: 'text', label: 'Título', section: 'Gráfico' },\n // KPI tem a altura do conteúdo: só título e largura\n span: common(0).span as FieldSpec,\n measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' },\n secondary: {\n kind: 'measure',\n label: 'Medida secundária',\n hint: 'Linha menor embaixo do número (ex.: volume, custo/t)',\n section: 'Dados'\n },\n labels: {\n ...LABELS_FIELD,\n hint: 'O da medida principal aparece acima do número; o da secundária, na linha de baixo'\n },\n formats: FORMATS_FIELD\n },\n combo: {\n ...common(360),\n maxLabels: {\n kind: 'integer',\n min: 0,\n max: 1000,\n default: 24,\n label: 'Máx. rótulos',\n hint: 'Acima dessa quantidade de barras, os valores aparecem só no tooltip',\n section: 'Gráfico'\n },\n dimension: { kind: 'dimension', required: true, label: 'Dimensão (eixo X)', section: 'Dados' },\n formatDimension,\n bar: { kind: 'measure', required: true, label: 'Barra', section: 'Dados' },\n line: { kind: 'measure', required: true, label: 'Linha', section: 'Dados' },\n detail: { kind: 'measure', label: 'Detalhe (2ª linha do rótulo)', section: 'Dados' },\n clickToFilter,\n hideEmpty,\n barColor: { kind: 'color', default: '#F58220', label: 'Cor da barra', section: 'Aparência' },\n lineColor: { kind: 'color', default: '#555555', label: 'Cor da linha', section: 'Aparência' },\n labels: LABELS_FIELD,\n formats: FORMATS_FIELD\n },\n bar: {\n ...common(360),\n dimension: {\n kind: 'dimension',\n required: true,\n label: 'Dimensão (categorias)',\n section: 'Dados'\n },\n formatDimension,\n measures: {\n kind: 'measures',\n required: true,\n label: 'Medidas (uma série cada)',\n section: 'Dados'\n },\n sort: {\n kind: 'enum',\n values: ['dimension', 'desc', 'asc'],\n labels: { dimension: 'pela dimensão', desc: 'maior primeiro', asc: 'menor primeiro' },\n default: 'dimension',\n label: 'Ordem',\n hint: 'Maior/menor primeiro usa a primeira medida',\n section: 'Dados'\n },\n limit: {\n kind: 'integer',\n min: 1,\n max: 1000,\n label: 'Só os N primeiros',\n hint: 'Aplicado depois de ordenar (ex.: top 10 navios)',\n section: 'Dados'\n },\n clickToFilter,\n hideEmpty,\n orientation: {\n kind: 'enum',\n values: ['vertical', 'horizontal'],\n default: 'vertical',\n label: 'Orientação',\n hint: 'Horizontal cabe nomes longos (navios, funções)',\n section: 'Aparência'\n },\n stacked: {\n kind: 'boolean',\n default: false,\n label: 'Empilhar séries',\n hint: 'Só faz sentido com medidas somáveis (partes de um total)',\n section: 'Aparência'\n },\n showValues: { kind: 'boolean', default: true, label: 'Mostrar valores', section: 'Aparência' },\n labels: LABELS_FIELD,\n formats: FORMATS_FIELD\n },\n donut: {\n ...common(300),\n dimension: { kind: 'dimension', required: true, label: 'Dimensão (fatias)', section: 'Dados' },\n formatDimension,\n measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' },\n maxSlices: {\n kind: 'integer',\n min: 2,\n max: 8,\n default: 6,\n label: 'Máx. fatias',\n hint: 'As menores além dessa quantidade viram \"Outros\"',\n section: 'Dados'\n },\n clickToFilter,\n labels: LABELS_FIELD,\n formats: FORMATS_FIELD,\n percentDecimals\n },\n table: {\n ...common(400),\n dimensions: { kind: 'dimensions', label: 'Dimensões (linhas)', section: 'Dados' },\n formatDimensions: {\n kind: 'formatters',\n label: 'Formatar dimensões',\n hint: 'Formatters passados ao <DashboardCharts formatters={{ ... }}> da página',\n section: 'Dados'\n },\n measures: { kind: 'measures', required: true, label: 'Medidas (colunas)', section: 'Dados' },\n percentOf: {\n kind: 'measures',\n label: '% do total',\n hint: 'Coluna extra com a participação de cada linha: valor da linha ÷ soma da coluna. Faz sentido com medidas somáveis (custo, volume), não com razões.',\n section: 'Dados'\n },\n sortBy: {\n kind: 'column',\n label: 'Ordenar por',\n hint: 'Ordem inicial; quem usa reordena clicando no cabeçalho',\n section: 'Dados'\n },\n sortDirection: {\n kind: 'enum',\n values: ['desc', 'asc'],\n labels: { desc: 'decrescente', asc: 'crescente' },\n default: 'desc',\n label: 'Direção',\n section: 'Dados'\n },\n showTotal: { kind: 'boolean', default: true, label: 'Linha de total', section: 'Dados' },\n clickToFilter: {\n ...clickToFilter,\n hint: 'Clicar numa linha filtra pela primeira dimensão'\n },\n labels: LABELS_FIELD,\n formats: FORMATS_FIELD,\n percentDecimals\n }\n}\n\n// --- registro de tipos ---\n\n/**\n * Tipo de gráfico: nome no devtools e campos do JSON. Para um tipo do app, `title`, `span` e\n * `height` entram sozinhos (`height: null` = sem altura, como o KPI) — `fields` tem só os do\n * componente. Objeto puro (sem React): o mesmo arquivo serve o client e o servidor.\n *\n * ```ts\n * export const rankingType = {\n * label: 'Ranking',\n * height: 300,\n * fields: {\n * dimension: { kind: 'dimension', required: true, label: 'Dimensão', section: 'Dados' },\n * measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' }\n * }\n * } satisfies ChartTypeSpec\n * ```\n */\nexport type ChartTypeSpec = {\n label: string\n fields: Record<string, FieldSpec>\n /** Altura padrão em px (padrão: 300); `null` = o componente tem a altura do conteúdo. */\n height?: number | null\n}\n\n/** Registro completo (lib + app), com os campos comuns já incluídos. Ver `resolveChartTypes`. */\nexport type ChartTypes = Record<string, { label: string; fields: Record<string, FieldSpec> }>\n\nexport const BUILTIN_CHART_TYPES: ChartTypes = Object.fromEntries(\n CHART_TYPES.map((type) => [type, { label: CHART_TYPE_LABEL[type], fields: CHART_FIELDS[type] }])\n)\n\n/**\n * Junta os tipos da lib com os do app. Nome igual a um tipo da lib ou fora do padrão de id é erro\n * (aparece na subida, não no meio da edição).\n */\nexport function resolveChartTypes(custom: Record<string, ChartTypeSpec> = {}): ChartTypes {\n const types: ChartTypes = { ...BUILTIN_CHART_TYPES }\n for (const [name, spec] of Object.entries(custom)) {\n if (BUILTIN_CHART_TYPES[name])\n throw new Error(`Tipo de gráfico \"${name}\" já existe na lib: escolha outro nome`)\n if (!LAYOUT_ID.test(name))\n throw new Error(`Tipo de gráfico \"${name}\": use letras, números, \"-\" e \"_\"`)\n const { title, span, height } = common(spec.height ?? 300)\n const base = spec.height === null ? { title, span } : { title, span, height }\n types[name] = {\n label: spec.label,\n fields: { ...base, ...spec.fields } as Record<string, FieldSpec>\n }\n }\n return types\n}\n\n/**\n * Props que o componente de um tipo do app recebe: os campos do gráfico no JSON (com os `default`\n * dos campos aplicados e os formatters já trocados pelas funções), sem `id`, `type`, `title` e\n * `span`, mais o `queryLabel` (nome da consulta no devtools).\n */\nexport function customChartProps(\n chart: ChartDef,\n types: ChartTypes,\n formatters: Record<string, unknown>\n): Record<string, unknown> {\n const values = chart as Record<string, unknown>\n const props: Record<string, unknown> = {}\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\n if (key === 'title' || key === 'span') continue\n const value = values[key] ?? spec.default\n if (value === undefined) continue\n if (spec.kind === 'formatter') props[key] = formatters[value as string]\n else if (spec.kind === 'formatters')\n props[key] = Object.fromEntries(\n Object.entries(value as Record<string, string>)\n .filter(([, name]) => formatters[name])\n .map(([dimension, name]) => [dimension, formatters[name]])\n )\n else props[key] = value\n }\n return { ...props, queryLabel: `${chart.title || chart.id} · ${chart.type}` }\n}\n\nexport const FILTER_FIELDS: Record<string, FieldSpec> = {\n dimension: { kind: 'dimension', required: true, label: 'Dimensão', section: 'Filtro' },\n label: {\n kind: 'text',\n label: 'Rótulo',\n hint: 'Padrão: rótulo da dimensão no dicionário',\n section: 'Filtro'\n },\n width: {\n kind: 'integer',\n min: 80,\n max: 800,\n default: 200,\n label: 'Largura (px)',\n section: 'Filtro'\n },\n formatLabel: {\n kind: 'formatter',\n label: 'Formatar opções',\n hint: 'Formatters passados ao <DashboardFilters formatters={{ ... }}> da página',\n section: 'Filtro'\n }\n}\n\nexport const CANVAS_FIELDS: Record<string, FieldSpec> = {\n width: {\n kind: 'integer',\n min: 320,\n max: 7680,\n required: true,\n label: 'Largura (px)',\n section: 'Tela'\n },\n height: {\n kind: 'integer',\n min: 200,\n max: 4320,\n label: 'Altura mínima (px)',\n hint: 'Conteúdo mais alto que isso estende a tela',\n section: 'Tela'\n }\n}\n\n/** Campos de `CardStyle` (o `card` de um gráfico). */\nexport const CARD_FIELDS: Record<string, FieldSpec> = {\n background: { kind: 'color', default: '#ffffff', label: 'Fundo', section: 'Card' },\n borderColor: { kind: 'color', default: '#e0e0e0', label: 'Cor da borda', section: 'Card' },\n borderWidth: {\n kind: 'integer',\n min: 0,\n max: 8,\n default: 1,\n label: 'Borda (px)',\n hint: '0 = sem borda',\n section: 'Card'\n },\n borderRadius: {\n kind: 'integer',\n min: 0,\n max: 40,\n default: 4,\n label: 'Cantos (px)',\n section: 'Card'\n },\n shadow: {\n kind: 'integer',\n min: 0,\n max: 24,\n default: 0,\n label: 'Sombra',\n hint: 'Elevação do MUI: 0 = sem sombra, 1 a 4 = sutil, acima disso = forte',\n section: 'Card'\n },\n padding: {\n kind: 'integer',\n min: 0,\n max: 64,\n default: 12,\n label: 'Espaço interno (px)',\n section: 'Card'\n },\n titleColor: { kind: 'color', default: '#212121', label: 'Cor do título', section: 'Card' },\n titleSize: {\n kind: 'integer',\n min: 10,\n max: 40,\n default: 16,\n label: 'Tamanho do título (px)',\n section: 'Card'\n },\n titleAlign: {\n kind: 'enum',\n values: ['left', 'center', 'right'],\n labels: { left: 'à esquerda', center: 'centralizado', right: 'à direita' },\n default: 'left',\n label: 'Alinhamento do título',\n section: 'Card'\n }\n}\n\n/** Campos de `CardsStyle` (`layout.cards`): o espaço entre os cards e o estilo padrão deles. */\nexport const CARDS_FIELDS: Record<string, FieldSpec> = {\n gap: {\n kind: 'integer',\n min: 0,\n max: 64,\n default: 12,\n label: 'Espaço entre cards (px)',\n section: 'Card'\n },\n ...CARD_FIELDS\n}\n\n/** Estilo de um card: o padrão do layout com o do gráfico por cima. */\nexport function chartCardStyle(layout: ChartsLayout, chart: ChartDef): CardStyle {\n const { gap: _gap, ...defaults } = layout.cards ?? {}\n return { ...defaults, ...chart.card }\n}\n\n/** Tamanhos comuns de tela para o devtools. */\nexport const CANVAS_PRESETS: CanvasDef[] = [\n { width: 1920, height: 1080 },\n { width: 1600, height: 900 },\n { width: 1366, height: 768 },\n { width: 1280, height: 720 }\n]\n\n// --- validação ---\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isName = (value: unknown): value is string => typeof value === 'string' && !!value\n\nfunction fieldError(spec: FieldSpec, value: unknown): string | null {\n if (value === undefined) return spec.required ? 'é obrigatório' : null\n switch (spec.kind) {\n case 'text':\n return typeof value === 'string' ? null : 'precisa ser texto'\n case 'color':\n return typeof value === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(value)\n ? null\n : 'precisa ser uma cor (#rrggbb)'\n case 'boolean':\n return typeof value === 'boolean' ? null : 'precisa ser true/false'\n case 'integer':\n return typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= spec.min &&\n value <= spec.max\n ? null\n : `precisa ser um inteiro de ${spec.min} a ${spec.max}`\n case 'enum':\n return spec.values.includes(value as string) ? null : `precisa ser ${spec.values.join(', ')}`\n case 'dimension':\n case 'measure':\n case 'formatter':\n case 'column':\n return isName(value) ? null : spec.required ? 'é obrigatório' : 'precisa ser texto'\n case 'dimensions':\n case 'measures':\n if (!Array.isArray(value) || !value.every(isName)) return 'precisa ser uma lista de nomes'\n if (spec.required && !value.length) return 'precisa de pelo menos um item'\n return new Set(value).size === value.length ? null : 'tem item repetido'\n case 'formatters':\n return isRecord(value) && Object.values(value).every(isName)\n ? null\n : 'precisa ser { dimensão: formatter }'\n case 'labels':\n return isRecord(value) && Object.values(value).every((label) => typeof label === 'string')\n ? null\n : 'precisa ser { coluna: rótulo }'\n case 'formats': {\n if (!isRecord(value)) return 'precisa ser { medida: formato }'\n for (const [measure, format] of Object.entries(value)) {\n const error = numberFormatError(format)\n if (error) return `\"${measure}\": ${error}`\n }\n return null\n }\n }\n}\n\nconst NUMBER_FORMAT_KEYS = ['format', 'decimals', 'compact', 'prefix', 'suffix']\n\nfunction numberFormatError(format: unknown): string | null {\n if (!isRecord(format)) return 'precisa ser um objeto'\n const unknown = Object.keys(format).find((key) => !NUMBER_FORMAT_KEYS.includes(key))\n if (unknown) return `campo desconhecido \\`${unknown}\\``\n const { decimals } = format\n if (format.format !== undefined && !VALUE_FORMATS.includes(format.format as ValueFormat))\n return `formato inválido \"${String(format.format)}\"`\n if (\n decimals !== undefined &&\n (typeof decimals !== 'number' || !Number.isInteger(decimals) || decimals < 0 || decimals > 10)\n )\n return '`decimals` precisa ser um inteiro de 0 a 10'\n if (format.compact !== undefined && typeof format.compact !== 'boolean')\n return '`compact` precisa ser true/false'\n for (const key of ['prefix', 'suffix'])\n if (format[key] !== undefined && typeof format[key] !== 'string')\n return `\\`${key}\\` precisa ser texto`\n return null\n}\n\n/**\n * Problemas de forma do layout (vazio = válido). Confere a forma do objeto porque o rascunho do\n * devtools chega como JSON da request.\n */\nexport function validateLayout(\n layout: ChartsLayout,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): string[] {\n if (!isRecord(layout)) return ['layout inválido']\n if (typeof layout.id !== 'string' || !LAYOUT_ID.test(layout.id))\n return ['`id` do layout: letras, números, \"-\" e \"_\" (até 80)']\n if (!Array.isArray(layout.charts)) return ['`charts` precisa ser uma lista']\n\n const errors: string[] = []\n const ids = new Set<string>()\n layout.charts.forEach((chart: unknown, index) => {\n const context = `gráfico ${index + 1}`\n if (!isRecord(chart)) {\n errors.push(`${context} inválido`)\n return\n }\n if (typeof chart.id !== 'string' || !LAYOUT_ID.test(chart.id))\n errors.push(`${context}: \\`id\\` inválido`)\n else if (ids.has(chart.id)) errors.push(`${context}: id repetido \"${chart.id}\"`)\n else ids.add(chart.id)\n\n const fields =\n typeof chart.type === 'string' && Object.hasOwn(types, chart.type)\n ? types[chart.type]?.fields\n : undefined\n if (!fields) {\n errors.push(`${context}: tipo inválido \"${chart.type}\"`)\n return\n }\n if (typeof chart.title !== 'string') errors.push(`${context}: \\`title\\` precisa ser texto`)\n const rest = Object.fromEntries(Object.entries(fields).filter(([key]) => key !== 'title'))\n errors.push(...fieldsErrors(context, rest, chart, ['id', 'type', 'title', 'card']))\n if (chart.card !== undefined) {\n if (!isRecord(chart.card)) errors.push(`${context}: \\`card\\` precisa ser um objeto`)\n else errors.push(...fieldsErrors(`${context}: card`, CARD_FIELDS, chart.card))\n }\n })\n\n if (layout.cards !== undefined) {\n if (!isRecord(layout.cards)) errors.push('`cards` precisa ser um objeto')\n else errors.push(...fieldsErrors('cards', CARDS_FIELDS, layout.cards))\n }\n\n if (layout.canvas !== undefined) {\n if (!isRecord(layout.canvas)) errors.push('`canvas` precisa ser { width, height? }')\n else errors.push(...fieldsErrors('canvas', CANVAS_FIELDS, layout.canvas))\n }\n\n if (layout.filters !== undefined) {\n if (!Array.isArray(layout.filters)) return [...errors, '`filters` precisa ser uma lista']\n const dimensions = new Set<string>()\n layout.filters.forEach((filter: unknown, index) => {\n const context = `filtro ${index + 1}`\n if (!isRecord(filter)) {\n errors.push(`${context} inválido`)\n return\n }\n errors.push(...fieldsErrors(context, FILTER_FIELDS, filter))\n if (isName(filter.dimension)) {\n if (dimensions.has(filter.dimension))\n errors.push(`${context}: dimensão repetida \"${filter.dimension}\"`)\n dimensions.add(filter.dimension)\n }\n })\n }\n return errors\n}\n\nfunction fieldsErrors(\n context: string,\n fields: Record<string, FieldSpec>,\n values: Record<string, unknown>,\n extraKeys: string[] = []\n) {\n const errors: string[] = []\n for (const [key, spec] of Object.entries(fields)) {\n const error = fieldError(spec, values[key])\n if (error) errors.push(`${context}: \\`${key}\\` ${error}`)\n }\n const known = new Set([...extraKeys, ...Object.keys(fields)])\n for (const key of Object.keys(values))\n if (!known.has(key)) errors.push(`${context}: campo desconhecido \\`${key}\\``)\n return errors\n}\n\n/** Dimensões, medidas e formatters que o gráfico referencia (lidos pelos campos do tipo). */\nexport const chartReferences = (chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) =>\n fieldReferences(types[chart.type]?.fields ?? {}, chart)\n\n/** Idem para um filtro da página. */\nexport const filterReferences = (filter: FilterDef) => fieldReferences(FILTER_FIELDS, filter)\n\nfunction fieldReferences(fields: Record<string, FieldSpec>, item: object) {\n const refs = { dimensions: [] as string[], measures: [] as string[], formatters: [] as string[] }\n const values = item as Record<string, unknown>\n for (const [key, spec] of Object.entries(fields)) {\n const value = values[key]\n if (value === undefined || value === '') continue\n if (spec.kind === 'dimension') refs.dimensions.push(value as string)\n if (spec.kind === 'dimensions') refs.dimensions.push(...(value as string[]))\n if (spec.kind === 'measure') refs.measures.push(value as string)\n if (spec.kind === 'measures') refs.measures.push(...(value as string[]))\n if (spec.kind === 'formatter') refs.formatters.push(value as string)\n if (spec.kind === 'formatters') refs.formatters.push(...Object.values(value as object))\n }\n return refs\n}\n\n/** Colunas que o gráfico mostra (para `sortBy`): as dimensões e medidas escolhidas nele. */\nexport function chartColumns(chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) {\n const { dimensions, measures } = chartReferences(chart, types)\n return [...dimensions, ...measures]\n}\n\n/**\n * Dimensões/medidas que o layout usa e o modelo não tem. Não impede salvar (o modelo pode estar\n * sendo editado junto), mas o gráfico vai mostrar erro.\n */\nexport function layoutModelWarnings(\n layout: ChartsLayout,\n model: Pick<ModelDef, 'dimensions' | 'measures'>,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): string[] {\n const warnings: string[] = []\n for (const chart of layout.charts) {\n const name = `\"${chart.title || chart.id}\"`\n const { dimensions, measures } = chartReferences(chart, types)\n for (const dimension of dimensions)\n if (!model.dimensions[dimension]) warnings.push(`${name}: dimensão \"${dimension}\" não existe`)\n for (const measure of measures)\n if (!model.measures[measure]) warnings.push(`${name}: medida \"${measure}\" não existe`)\n // Campos `column` (ex.: `sortBy`) e as chaves de `labels`/`formats` precisam ser colunas\n // escolhidas no gráfico\n const columns = chartColumns(chart, types)\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\n const value = (chart as Record<string, unknown>)[key]\n if (spec.kind === 'column' && isName(value) && !columns.includes(value))\n warnings.push(`${name}: \"${value}\" (${key}) não é coluna do gráfico`)\n if ((spec.kind === 'labels' || spec.kind === 'formats') && isRecord(value))\n for (const column of Object.keys(value))\n if (!columns.includes(column))\n warnings.push(`${name}: \"${column}\" (${key}) não é coluna do gráfico`)\n }\n }\n for (const filter of layout.filters ?? [])\n if (!model.dimensions[filter.dimension])\n warnings.push(\n `filtro \"${filter.label || filter.dimension}\": dimensão \"${filter.dimension}\" não existe`\n )\n return warnings\n}\n\n/**\n * Gráfico novo de um tipo, reaproveitando o que der do anterior (troca de tipo no devtools) ou\n * escolhendo dimensão/medidas do modelo.\n */\nexport function chartOfType(\n type: string,\n model: Pick<ModelDef, 'dimensions' | 'measures'>,\n base: Pick<BaseChartDef, 'id' | 'title' | 'span'> & Partial<ChartDef>,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): ChartDef {\n const { id, title, span, card, labels, formats } = base as BaseChartDef & ColumnsDisplay\n const previous = chartReferences(base as ChartDef, types)\n const measures = Object.entries(model.measures)\n const firstSum = measures.find(([, measure]) => isAggregate(measure))?.[0]\n const firstRatio = measures.find(([, measure]) => measure.type === 'ratio')?.[0]\n const dimension = previous.dimensions[0] ?? Object.keys(model.dimensions)[0] ?? ''\n const measure = previous.measures[0] ?? firstSum ?? measures[0]?.[0] ?? ''\n // Rótulos e formatos seguem se o tipo novo tiver os campos (as chaves que sobrarem viram aviso)\n const fields = types[type]?.fields ?? {}\n const shared = {\n id,\n title,\n ...(span !== undefined && { span }),\n ...(card && { card }),\n ...(labels && fields.labels?.kind === 'labels' && { labels }),\n ...(formats && fields.formats?.kind === 'formats' && { formats })\n }\n switch (type) {\n case 'kpi':\n return {\n ...shared,\n type,\n measure,\n ...(previous.measures[1] && { secondary: previous.measures[1] })\n }\n case 'combo':\n return {\n ...shared,\n type,\n dimension,\n bar: measure,\n line: previous.measures[1] ?? firstRatio ?? measure\n }\n case 'bar':\n return {\n ...shared,\n type,\n dimension,\n measures: previous.measures.length ? previous.measures : [measure]\n }\n case 'donut':\n return { ...shared, type, dimension, measure }\n case 'table':\n return {\n ...shared,\n type,\n dimensions: previous.dimensions.length ? previous.dimensions : [dimension],\n measures: previous.measures.length ? previous.measures : [measure]\n }\n }\n\n // Tipo do app: preenche os campos obrigatórios, na ordem, com o que já estava escolhido\n const chart: CustomChartDef = { ...shared, type }\n const used = { dimensions: 0, measures: 0 }\n for (const [key, spec] of Object.entries(fields)) {\n if (!spec.required || key === 'title' || key === 'span') continue\n switch (spec.kind) {\n case 'dimension':\n chart[key] = previous.dimensions[used.dimensions++] ?? dimension\n break\n case 'measure':\n chart[key] = previous.measures[used.measures++] ?? measure\n break\n case 'dimensions':\n chart[key] = previous.dimensions.length ? previous.dimensions : [dimension]\n break\n case 'measures':\n chart[key] = previous.measures.length ? previous.measures : [measure]\n break\n case 'enum':\n chart[key] = spec.default ?? spec.values[0]\n break\n case 'integer':\n chart[key] = spec.default ?? spec.min\n break\n case 'boolean':\n chart[key] = spec.default ?? false\n break\n case 'text':\n case 'color':\n if (spec.default !== undefined) chart[key] = spec.default\n break\n }\n }\n return chart\n}\n\n/** Conteúdo do arquivo `<id>.json`. */\nexport function layoutFileContent(layout: ChartsLayout) {\n return `${JSON.stringify(layout, null, 2)}\\n`\n}\n"],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import { B as
|
|
1
|
+
import { B as FormulaMeasure, C as ChartTypeSpec, F as validateLayout, G as RatioMeasure, H as MeasureMeta, I as AggregateMeasure, J as TableDef, K as RelationshipDef, L as Aggregation, N as layoutModelWarnings, P as resolveChartTypes, R as DimensionDef, T as ChartsLayout, U as ModelDef, V as MeasureDef, X as modelMeta, Y as ValueFormat, Z as validateModel, a as DashboardResult, o as DashboardRow, q as SumMeasure, r as DashboardQuery, z as FieldMeta } from "../types-HXAn-3I1.js";
|
|
2
2
|
//#region src/codegen.d.ts
|
|
3
|
-
/**
|
|
4
|
-
* Gera o código TS de um modelo (`defineModel({...})`). Usado pelo "Copiar TS" do devtools e pelo
|
|
5
|
-
* "Salvar no arquivo" do servidor, que troca só a chamada do `defineModel` dentro do arquivo.
|
|
6
|
-
*/
|
|
7
3
|
/** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */
|
|
8
4
|
declare function canonicalJson(value: unknown): string;
|
|
9
5
|
/** Arquivo completo, pronto para colar em `src/dashboard/models/<nome>.ts`. */
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as modelEdges, D as validateModel, E as modelMeta, M as reachFrom, N as referencedTables, O as joinPlan, T as isAggregate, _ as canonicalJson, b as toModelCode, g as validateLayout, h as resolveChartTypes, j as parseColumnRef, k as measureHome, m as layoutModelWarnings, p as layoutFileContent, v as modelImports, w as aggregateSql, x as AGGREGATIONS, y as modelObjectCode } from "../layout-
|
|
1
|
+
import { A as modelEdges, D as validateModel, E as modelMeta, M as reachFrom, N as referencedTables, O as joinPlan, T as isAggregate, _ as canonicalJson, b as toModelCode, g as validateLayout, h as resolveChartTypes, j as parseColumnRef, k as measureHome, m as layoutModelWarnings, p as layoutFileContent, v as modelImports, w as aggregateSql, x as AGGREGATIONS, y as modelObjectCode } from "../layout-676sEvvb.js";
|
|
2
2
|
import { n as diffRows, t as describeDiffs } from "../compare-DloZhg1T.js";
|
|
3
3
|
//#region src/server/compile.ts
|
|
4
4
|
var DashboardQueryError = class extends Error {};
|
|
@@ -86,13 +86,17 @@ type ModelDef = {
|
|
|
86
86
|
dimensions: Record<string, DimensionDef>;
|
|
87
87
|
measures: Record<string, MeasureDef>;
|
|
88
88
|
/**
|
|
89
|
-
* Posição de cada card no diagrama do devtools (tabela -> canto superior esquerdo)
|
|
89
|
+
* Posição de cada card no diagrama do devtools (tabela -> canto superior esquerdo) e, se foi
|
|
90
|
+
* redimensionado, o tamanho. Só visual: não muda nenhuma consulta. Card sem posição entra no
|
|
91
|
+
* layout automático; sem tamanho, tem a largura padrão e a altura do conteúdo.
|
|
90
92
|
*/
|
|
91
93
|
diagram?: Record<string, DiagramPosition>;
|
|
92
94
|
};
|
|
93
95
|
type DiagramPosition = {
|
|
94
96
|
x: number;
|
|
95
97
|
y: number;
|
|
98
|
+
width?: number;
|
|
99
|
+
height?: number;
|
|
96
100
|
};
|
|
97
101
|
/** Só os metadados (sem SQL): é o que o client recebe fora do devtools. */
|
|
98
102
|
type ModelMeta = {
|
|
@@ -128,15 +132,34 @@ type BaseChartDef = {
|
|
|
128
132
|
/** Aparência do card deste gráfico (sobrepõe o `cards` do layout). */
|
|
129
133
|
card?: CardStyle;
|
|
130
134
|
};
|
|
131
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Formato de uma medida num gráfico: sobrepõe o `format`/`decimals` do dicionário do modelo só
|
|
137
|
+
* naquele gráfico.
|
|
138
|
+
*/
|
|
139
|
+
type ChartNumberFormat = {
|
|
140
|
+
format?: ValueFormat;
|
|
141
|
+
/** Casas decimais (0 a 10). */
|
|
142
|
+
decimals?: number;
|
|
143
|
+
/** Notação curta: 1,2 mil / 3,4 mi. */
|
|
144
|
+
compact?: boolean;
|
|
145
|
+
/** Texto antes do número (ex.: "US$ "). */
|
|
146
|
+
prefix?: string;
|
|
147
|
+
/** Texto depois do número (ex.: " t"). */
|
|
148
|
+
suffix?: string;
|
|
149
|
+
};
|
|
150
|
+
/** Rótulos e formatos do gráfico, por nome de dimensão/medida (sobrepõem o dicionário). */
|
|
151
|
+
type ColumnsDisplay = {
|
|
152
|
+
/** `{ dimensãoOuMedida: 'Rótulo' }`: legenda, tooltip e cabeçalho. */
|
|
153
|
+
labels?: Record<string, string>;
|
|
154
|
+
/** `{ medida: { decimals: 1, ... } }`. */
|
|
155
|
+
formats?: Record<string, ChartNumberFormat>;
|
|
156
|
+
};
|
|
157
|
+
type ComboChartDef = BaseChartDef & ColumnsDisplay & {
|
|
132
158
|
type: 'combo';
|
|
133
159
|
dimension: string;
|
|
134
160
|
bar: string;
|
|
135
161
|
line: string;
|
|
136
162
|
detail?: string;
|
|
137
|
-
barLabel?: string;
|
|
138
|
-
lineLabel?: string;
|
|
139
|
-
detailLabel?: string;
|
|
140
163
|
/** Nome de um formatter passado ao `<DashboardCharts formatters>` (funções não cabem no JSON). */
|
|
141
164
|
formatDimension?: string;
|
|
142
165
|
clickToFilter?: boolean;
|
|
@@ -145,7 +168,7 @@ type ComboChartDef = BaseChartDef & {
|
|
|
145
168
|
barColor?: string;
|
|
146
169
|
lineColor?: string;
|
|
147
170
|
};
|
|
148
|
-
type BarChartDef = BaseChartDef & {
|
|
171
|
+
type BarChartDef = BaseChartDef & ColumnsDisplay & {
|
|
149
172
|
type: 'bar';
|
|
150
173
|
dimension: string;
|
|
151
174
|
/** Uma série por medida. */
|
|
@@ -161,16 +184,18 @@ type BarChartDef = BaseChartDef & {
|
|
|
161
184
|
hideEmpty?: boolean;
|
|
162
185
|
showValues?: boolean;
|
|
163
186
|
};
|
|
164
|
-
type DonutChartDef = BaseChartDef & {
|
|
187
|
+
type DonutChartDef = BaseChartDef & ColumnsDisplay & {
|
|
165
188
|
type: 'donut';
|
|
166
189
|
dimension: string;
|
|
167
190
|
measure: string;
|
|
168
191
|
/** Fatias além dessa quantidade viram "Outros". */
|
|
169
192
|
maxSlices?: number;
|
|
193
|
+
/** Casas decimais do percentual. Padrão: 1. */
|
|
194
|
+
percentDecimals?: number;
|
|
170
195
|
formatDimension?: string;
|
|
171
196
|
clickToFilter?: boolean;
|
|
172
197
|
};
|
|
173
|
-
type TableChartDef = BaseChartDef & {
|
|
198
|
+
type TableChartDef = BaseChartDef & ColumnsDisplay & {
|
|
174
199
|
type: 'table';
|
|
175
200
|
dimensions?: string[];
|
|
176
201
|
measures: string[];
|
|
@@ -179,6 +204,8 @@ type TableChartDef = BaseChartDef & {
|
|
|
179
204
|
* está em `measures` é consultada só para o percentual.
|
|
180
205
|
*/
|
|
181
206
|
percentOf?: string[];
|
|
207
|
+
/** Casas decimais das colunas de percentual. Padrão: 1. */
|
|
208
|
+
percentDecimals?: number;
|
|
182
209
|
/** Formatter por dimensão (`{ mes: 'nomeMes' }`). */
|
|
183
210
|
formatDimensions?: Record<string, string>;
|
|
184
211
|
/** Linha de total, consultada sem as dimensões (razões saem certas, não somadas). */
|
|
@@ -188,13 +215,12 @@ type TableChartDef = BaseChartDef & {
|
|
|
188
215
|
sortDirection?: 'asc' | 'desc';
|
|
189
216
|
clickToFilter?: boolean;
|
|
190
217
|
};
|
|
191
|
-
type KpiChartDef = Omit<BaseChartDef, 'height'> & {
|
|
218
|
+
type KpiChartDef = Omit<BaseChartDef, 'height'> & ColumnsDisplay & {
|
|
192
219
|
type: 'kpi';
|
|
193
220
|
/** Medida do número grande (total com os filtros da página). */
|
|
194
221
|
measure: string;
|
|
195
222
|
/** Medida extra numa linha menor embaixo (ex.: volume, custo/t). */
|
|
196
223
|
secondary?: string;
|
|
197
|
-
secondaryLabel?: string;
|
|
198
224
|
};
|
|
199
225
|
/** Gráficos da lib (`type` em `CHART_TYPES`). */
|
|
200
226
|
type BuiltinChartDef = KpiChartDef | ComboChartDef | BarChartDef | DonutChartDef | TableChartDef;
|
|
@@ -261,7 +287,7 @@ type FieldSpec = {
|
|
|
261
287
|
label: string;
|
|
262
288
|
hint?: string;
|
|
263
289
|
/** Agrupamento no formulário do devtools. */
|
|
264
|
-
section: 'Gráfico' | 'Dados' | 'Aparência' | 'Filtro' | 'Tela' | 'Card';
|
|
290
|
+
section: 'Gráfico' | 'Dados' | 'Aparência' | 'Rótulos e formatos' | 'Filtro' | 'Tela' | 'Card';
|
|
265
291
|
required?: boolean;
|
|
266
292
|
/** Valor que o componente usa quando o campo não está no JSON. */
|
|
267
293
|
default?: unknown;
|
|
@@ -297,6 +323,14 @@ type FieldSpec = {
|
|
|
297
323
|
/** Uma das dimensões/medidas escolhidas no próprio gráfico. */
|
|
298
324
|
{
|
|
299
325
|
kind: 'column';
|
|
326
|
+
} |
|
|
327
|
+
/** `{ dimensãoOuMedida: rótulo }` das colunas do gráfico. */
|
|
328
|
+
{
|
|
329
|
+
kind: 'labels';
|
|
330
|
+
} |
|
|
331
|
+
/** `{ medida: ChartNumberFormat }` das medidas do gráfico. */
|
|
332
|
+
{
|
|
333
|
+
kind: 'formats';
|
|
300
334
|
});
|
|
301
335
|
/**
|
|
302
336
|
* Tipo de gráfico: nome no devtools e campos do JSON. Para um tipo do app, `title`, `span` e
|
|
@@ -485,5 +519,5 @@ type DashboardResult = {
|
|
|
485
519
|
};
|
|
486
520
|
};
|
|
487
521
|
//#endregion
|
|
488
|
-
export {
|
|
489
|
-
//# sourceMappingURL=types-
|
|
522
|
+
export { FilterDef as A, FormulaMeasure as B, ChartTypeSpec as C, CustomChartDef as D, ComboChartDef as E, validateLayout as F, RatioMeasure as G, MeasureMeta as H, AggregateMeasure as I, TableDef as J, RelationshipDef as K, Aggregation as L, TableChartDef as M, layoutModelWarnings as N, DonutChartDef as O, resolveChartTypes as P, DimensionDef as R, ChartType as S, ChartsLayout as T, ModelDef as U, MeasureDef as V, ModelMeta as W, modelMeta as X, ValueFormat as Y, validateModel as Z, CanvasDef as _, DashboardResult as a, ChartDef as b, DevtoolsCapabilities as c, ModelValidation as d, ProfileStep as f, BuiltinChartDef as g, BarChartDef as h, DashboardQueryDebug as i, KpiChartDef as j, FieldSpec as k, FilterValue as l, SchemaTable as m, DashboardFilters as n, DashboardRow as o, SaveModelResult as p, SumMeasure as q, DashboardQuery as r, DashboardSnapshot as s, DashboardDevtoolsRequest as t, ModelColumns as u, CardStyle as v, ChartTypes as w, ChartNumberFormat as x, CardsStyle as y, FieldMeta as z };
|
|
523
|
+
//# sourceMappingURL=types-HXAn-3I1.d.ts.map
|
package/dist/types.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
export { type BarChartDef, type BuiltinChartDef, type CanvasDef, type CardStyle, type CardsStyle, type ChartDef, type ChartType, type ChartTypeSpec, type ChartTypes, type ChartsLayout, type ComboChartDef, type CustomChartDef, DashboardDevtoolsRequest, DashboardFilters, DashboardQuery, DashboardQueryDebug, DashboardResult, DashboardRow, DashboardSnapshot, DevtoolsCapabilities, type DimensionDef, type DonutChartDef, type FieldMeta, type FieldSpec, type FilterDef, FilterValue, type FormulaMeasure, type KpiChartDef, type MeasureDef, type MeasureMeta, ModelColumns, type ModelDef, type ModelMeta, ModelValidation, ProfileStep, type RelationshipDef, SaveModelResult, SchemaTable, type TableChartDef, type TableDef, type ValueFormat };
|
|
1
|
+
import { A as FilterDef, B as FormulaMeasure, C as ChartTypeSpec, D as CustomChartDef, E as ComboChartDef, H as MeasureMeta, J as TableDef, K as RelationshipDef, M as TableChartDef, O as DonutChartDef, R as DimensionDef, S as ChartType, T as ChartsLayout, U as ModelDef, V as MeasureDef, W as ModelMeta, Y as ValueFormat, _ as CanvasDef, a as DashboardResult, b as ChartDef, c as DevtoolsCapabilities, d as ModelValidation, f as ProfileStep, g as BuiltinChartDef, h as BarChartDef, i as DashboardQueryDebug, j as KpiChartDef, k as FieldSpec, l as FilterValue, m as SchemaTable, n as DashboardFilters, o as DashboardRow, p as SaveModelResult, r as DashboardQuery, s as DashboardSnapshot, t as DashboardDevtoolsRequest, u as ModelColumns, v as CardStyle, w as ChartTypes, x as ChartNumberFormat, y as CardsStyle, z as FieldMeta } from "./types-HXAn-3I1.js";
|
|
2
|
+
export { type BarChartDef, type BuiltinChartDef, type CanvasDef, type CardStyle, type CardsStyle, type ChartDef, type ChartNumberFormat, type ChartType, type ChartTypeSpec, type ChartTypes, type ChartsLayout, type ComboChartDef, type CustomChartDef, DashboardDevtoolsRequest, DashboardFilters, DashboardQuery, DashboardQueryDebug, DashboardResult, DashboardRow, DashboardSnapshot, DevtoolsCapabilities, type DimensionDef, type DonutChartDef, type FieldMeta, type FieldSpec, type FilterDef, FilterValue, type FormulaMeasure, type KpiChartDef, type MeasureDef, type MeasureMeta, ModelColumns, type ModelDef, type ModelMeta, ModelValidation, ProfileStep, type RelationshipDef, SaveModelResult, SchemaTable, type TableChartDef, type TableDef, type ValueFormat };
|