@bluemarble/bi 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -77
- package/dist/{DashboardDevtools-D0pzwSgn.js → DashboardDevtools-TFwjFdqn.js} +1437 -565
- package/dist/DashboardDevtools-TFwjFdqn.js.map +1 -0
- package/dist/{ModelDictionary-Cw4sa8tW.js → ModelDictionary-g5d84D0n.js} +67 -5
- package/dist/ModelDictionary-g5d84D0n.js.map +1 -0
- package/dist/cli.js +0 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +48 -9
- package/dist/client/index.js +186 -45
- package/dist/client/index.js.map +1 -1
- package/dist/codegen-LoQzFAle.js.map +1 -1
- package/dist/compare-DloZhg1T.js.map +1 -1
- package/dist/{formatRules-DxA9vMvz.d.ts → formatRules-C1PW2EiL.d.ts} +74 -2
- package/dist/{layout-BS3nO6OT.js → layout-BuZhfoRI.js} +167 -15
- package/dist/layout-BuZhfoRI.js.map +1 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +17 -1
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +81 -81
- package/dist/DashboardDevtools-D0pzwSgn.js.map +0 -1
- package/dist/ModelDictionary-Cw4sa8tW.js.map +0 -1
- package/dist/layout-BS3nO6OT.js.map +0 -1
|
@@ -344,6 +344,27 @@ const hideEmpty = {
|
|
|
344
344
|
label: "Esconder grupos vazios",
|
|
345
345
|
section: "Dados"
|
|
346
346
|
};
|
|
347
|
+
/** Filtros (por dimensão) que o gráfico não respeita; comum a todos os tipos. */
|
|
348
|
+
const ignoreFiltersField = {
|
|
349
|
+
kind: "filters",
|
|
350
|
+
label: "Ignorar filtros",
|
|
351
|
+
hint: "Dimensões cujos filtros este gráfico não respeita (slicers do painel ou clique em outro gráfico); vazio = respeita todos",
|
|
352
|
+
section: "Comportamento"
|
|
353
|
+
};
|
|
354
|
+
/** Vincula a medida do gráfico a um parâmetro de medida do layout (rosca/KPI). */
|
|
355
|
+
const measureParameterField = {
|
|
356
|
+
kind: "parameter",
|
|
357
|
+
label: "Medida pelo parâmetro",
|
|
358
|
+
hint: "Segue o slicer de medida escolhido; vazio = usa a medida fixa acima",
|
|
359
|
+
section: "Dados"
|
|
360
|
+
};
|
|
361
|
+
/** Título da rosca/KPI, com o token `{{medida}}` (vira a descrição da medida atual). */
|
|
362
|
+
const measureTitleField = {
|
|
363
|
+
kind: "text",
|
|
364
|
+
label: "Título",
|
|
365
|
+
hint: "Use {{medida}} para a descrição da medida atual (muda com o parâmetro de medida)",
|
|
366
|
+
section: "Gráfico"
|
|
367
|
+
};
|
|
347
368
|
/**
|
|
348
369
|
* Rótulo de cada dimensão/medida do gráfico. Um tipo do app ganha o mesmo editor com um campo
|
|
349
370
|
* `{ kind: 'labels' }` (o componente recebe `labels`; ver `fieldLabel`) e `{ kind: 'formats' }`.
|
|
@@ -371,11 +392,7 @@ const percentDecimals = {
|
|
|
371
392
|
};
|
|
372
393
|
const CHART_FIELDS = {
|
|
373
394
|
kpi: {
|
|
374
|
-
title:
|
|
375
|
-
kind: "text",
|
|
376
|
-
label: "Título",
|
|
377
|
-
section: "Gráfico"
|
|
378
|
-
},
|
|
395
|
+
title: measureTitleField,
|
|
379
396
|
span: common(0).span,
|
|
380
397
|
measure: {
|
|
381
398
|
kind: "measure",
|
|
@@ -389,11 +406,13 @@ const CHART_FIELDS = {
|
|
|
389
406
|
hint: "Linha menor embaixo do número (ex.: volume, custo/t)",
|
|
390
407
|
section: "Dados"
|
|
391
408
|
},
|
|
409
|
+
measureParameter: measureParameterField,
|
|
392
410
|
labels: {
|
|
393
411
|
...LABELS_FIELD,
|
|
394
412
|
hint: "O da medida principal aparece acima do número; o da secundária, na linha de baixo"
|
|
395
413
|
},
|
|
396
|
-
formats: FORMATS_FIELD
|
|
414
|
+
formats: FORMATS_FIELD,
|
|
415
|
+
ignoreFilters: ignoreFiltersField
|
|
397
416
|
},
|
|
398
417
|
combo: {
|
|
399
418
|
...common(360),
|
|
@@ -445,7 +464,8 @@ const CHART_FIELDS = {
|
|
|
445
464
|
section: "Aparência"
|
|
446
465
|
},
|
|
447
466
|
labels: LABELS_FIELD,
|
|
448
|
-
formats: FORMATS_FIELD
|
|
467
|
+
formats: FORMATS_FIELD,
|
|
468
|
+
ignoreFilters: ignoreFiltersField
|
|
449
469
|
},
|
|
450
470
|
bar: {
|
|
451
471
|
...common(360),
|
|
@@ -601,10 +621,12 @@ const CHART_FIELDS = {
|
|
|
601
621
|
section: "Valores"
|
|
602
622
|
},
|
|
603
623
|
labels: LABELS_FIELD,
|
|
604
|
-
formats: FORMATS_FIELD
|
|
624
|
+
formats: FORMATS_FIELD,
|
|
625
|
+
ignoreFilters: ignoreFiltersField
|
|
605
626
|
},
|
|
606
627
|
donut: {
|
|
607
628
|
...common(300),
|
|
629
|
+
title: measureTitleField,
|
|
608
630
|
dimension: {
|
|
609
631
|
kind: "dimension",
|
|
610
632
|
required: true,
|
|
@@ -618,6 +640,7 @@ const CHART_FIELDS = {
|
|
|
618
640
|
label: "Medida",
|
|
619
641
|
section: "Dados"
|
|
620
642
|
},
|
|
643
|
+
measureParameter: measureParameterField,
|
|
621
644
|
maxSlices: {
|
|
622
645
|
kind: "integer",
|
|
623
646
|
min: 2,
|
|
@@ -669,7 +692,8 @@ const CHART_FIELDS = {
|
|
|
669
692
|
},
|
|
670
693
|
labels: LABELS_FIELD,
|
|
671
694
|
formats: FORMATS_FIELD,
|
|
672
|
-
percentDecimals
|
|
695
|
+
percentDecimals,
|
|
696
|
+
ignoreFilters: ignoreFiltersField
|
|
673
697
|
},
|
|
674
698
|
table: {
|
|
675
699
|
...common(400),
|
|
@@ -744,6 +768,24 @@ const CHART_FIELDS = {
|
|
|
744
768
|
label: "Cor das bordas",
|
|
745
769
|
section: "Aparência"
|
|
746
770
|
},
|
|
771
|
+
cellPadding: {
|
|
772
|
+
kind: "integer",
|
|
773
|
+
min: 0,
|
|
774
|
+
max: 40,
|
|
775
|
+
default: 8,
|
|
776
|
+
label: "Espaçamento das células (px)",
|
|
777
|
+
hint: "Espaço em cima e embaixo de cada célula",
|
|
778
|
+
section: "Aparência"
|
|
779
|
+
},
|
|
780
|
+
fontSize: {
|
|
781
|
+
kind: "integer",
|
|
782
|
+
min: 8,
|
|
783
|
+
max: 32,
|
|
784
|
+
default: 13,
|
|
785
|
+
label: "Tamanho da fonte (px)",
|
|
786
|
+
hint: "Fonte das células e do cabeçalho",
|
|
787
|
+
section: "Aparência"
|
|
788
|
+
},
|
|
747
789
|
labels: LABELS_FIELD,
|
|
748
790
|
formats: FORMATS_FIELD,
|
|
749
791
|
align: {
|
|
@@ -770,7 +812,8 @@ const CHART_FIELDS = {
|
|
|
770
812
|
label: "Escalas de cor",
|
|
771
813
|
hint: "Gradiente numa coluna numérica: a cor vai da mínima à máxima conforme o valor (ex.: 0 a 100). Mínimo/máximo vazios = menor/maior valor da coluna na tabela. As regras ficam por cima.",
|
|
772
814
|
section: "Formatação condicional"
|
|
773
|
-
}
|
|
815
|
+
},
|
|
816
|
+
ignoreFilters: ignoreFiltersField
|
|
774
817
|
}
|
|
775
818
|
};
|
|
776
819
|
const BUILTIN_CHART_TYPES = Object.fromEntries(CHART_TYPES.map((type) => [type, {
|
|
@@ -789,11 +832,13 @@ function resolveChartTypes(custom = {}) {
|
|
|
789
832
|
const { title, span, height } = common(spec.height ?? 300);
|
|
790
833
|
const base = spec.height === null ? {
|
|
791
834
|
title,
|
|
792
|
-
span
|
|
835
|
+
span,
|
|
836
|
+
ignoreFilters: ignoreFiltersField
|
|
793
837
|
} : {
|
|
794
838
|
title,
|
|
795
839
|
span,
|
|
796
|
-
height
|
|
840
|
+
height,
|
|
841
|
+
ignoreFilters: ignoreFiltersField
|
|
797
842
|
};
|
|
798
843
|
types[name] = {
|
|
799
844
|
label: spec.label,
|
|
@@ -852,6 +897,49 @@ const FILTER_FIELDS = {
|
|
|
852
897
|
label: "Formatar opções",
|
|
853
898
|
hint: "Formatters passados ao <DashboardFilters formatters={{ ... }}> da página",
|
|
854
899
|
section: "Filtro"
|
|
900
|
+
},
|
|
901
|
+
ignoredBy: {
|
|
902
|
+
kind: "charts",
|
|
903
|
+
label: "Gráficos que ignoram",
|
|
904
|
+
hint: "Gráficos que não reagem a este filtro; vazio = todos reagem",
|
|
905
|
+
section: "Filtro"
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
/** Campos do formulário de um parâmetro de medida no devtools (ver `ParameterDef`). */
|
|
909
|
+
const PARAMETER_FIELDS = {
|
|
910
|
+
name: {
|
|
911
|
+
kind: "text",
|
|
912
|
+
required: true,
|
|
913
|
+
label: "Id",
|
|
914
|
+
hint: "Identificador do parâmetro (letras, números, \"-\" e \"_\")",
|
|
915
|
+
section: "Filtro"
|
|
916
|
+
},
|
|
917
|
+
label: {
|
|
918
|
+
kind: "text",
|
|
919
|
+
label: "Rótulo",
|
|
920
|
+
hint: "Padrão: \"Medida\"",
|
|
921
|
+
section: "Filtro"
|
|
922
|
+
},
|
|
923
|
+
measures: {
|
|
924
|
+
kind: "measures",
|
|
925
|
+
required: true,
|
|
926
|
+
label: "Medidas (opções)",
|
|
927
|
+
hint: "Medidas que o slicer oferece",
|
|
928
|
+
section: "Filtro"
|
|
929
|
+
},
|
|
930
|
+
default: {
|
|
931
|
+
kind: "column",
|
|
932
|
+
label: "Medida inicial",
|
|
933
|
+
hint: "Padrão: a primeira das opções",
|
|
934
|
+
section: "Filtro"
|
|
935
|
+
},
|
|
936
|
+
width: {
|
|
937
|
+
kind: "integer",
|
|
938
|
+
min: 80,
|
|
939
|
+
max: 800,
|
|
940
|
+
default: 200,
|
|
941
|
+
label: "Largura (px)",
|
|
942
|
+
section: "Filtro"
|
|
855
943
|
}
|
|
856
944
|
};
|
|
857
945
|
const CANVAS_FIELDS = {
|
|
@@ -949,6 +1037,37 @@ const CARD_FIELDS = {
|
|
|
949
1037
|
default: "left",
|
|
950
1038
|
label: "Alinhamento do título",
|
|
951
1039
|
section: "Card"
|
|
1040
|
+
},
|
|
1041
|
+
titleDivider: {
|
|
1042
|
+
kind: "boolean",
|
|
1043
|
+
default: false,
|
|
1044
|
+
label: "Divisor do título",
|
|
1045
|
+
hint: "Linha abaixo do título separando do conteúdo",
|
|
1046
|
+
section: "Card"
|
|
1047
|
+
},
|
|
1048
|
+
titleDividerColor: {
|
|
1049
|
+
kind: "color",
|
|
1050
|
+
default: "#e0e0e0",
|
|
1051
|
+
label: "Cor do divisor",
|
|
1052
|
+
section: "Card"
|
|
1053
|
+
},
|
|
1054
|
+
titleGap: {
|
|
1055
|
+
kind: "integer",
|
|
1056
|
+
min: 0,
|
|
1057
|
+
max: 40,
|
|
1058
|
+
default: 4,
|
|
1059
|
+
label: "Espaço título → divisor (px)",
|
|
1060
|
+
hint: "Só com o divisor ligado",
|
|
1061
|
+
section: "Card"
|
|
1062
|
+
},
|
|
1063
|
+
contentGap: {
|
|
1064
|
+
kind: "integer",
|
|
1065
|
+
min: 0,
|
|
1066
|
+
max: 40,
|
|
1067
|
+
default: 8,
|
|
1068
|
+
label: "Espaço título → conteúdo (px)",
|
|
1069
|
+
hint: "Abaixo do título (ou do divisor), antes do gráfico",
|
|
1070
|
+
section: "Card"
|
|
952
1071
|
}
|
|
953
1072
|
};
|
|
954
1073
|
const gapField = (label) => ({
|
|
@@ -1012,10 +1131,13 @@ function fieldError(spec, value) {
|
|
|
1012
1131
|
case "dimension":
|
|
1013
1132
|
case "measure":
|
|
1014
1133
|
case "formatter":
|
|
1015
|
-
case "column":
|
|
1134
|
+
case "column":
|
|
1135
|
+
case "parameter": return isName(value) ? null : spec.required ? "é obrigatório" : "precisa ser texto";
|
|
1016
1136
|
case "dimensions":
|
|
1017
1137
|
case "measures":
|
|
1018
1138
|
case "columnOrder":
|
|
1139
|
+
case "filters":
|
|
1140
|
+
case "charts":
|
|
1019
1141
|
if (!Array.isArray(value) || !value.every(isName)) return "precisa ser uma lista de nomes";
|
|
1020
1142
|
if (spec.required && !value.length) return "precisa de pelo menos um item";
|
|
1021
1143
|
return new Set(value).size === value.length ? null : "tem item repetido";
|
|
@@ -1131,6 +1253,29 @@ function validateLayout(layout, types = BUILTIN_CHART_TYPES) {
|
|
|
1131
1253
|
if (dimensions.has(filter.dimension)) errors.push(`${context}: dimensão repetida "${filter.dimension}"`);
|
|
1132
1254
|
dimensions.add(filter.dimension);
|
|
1133
1255
|
}
|
|
1256
|
+
if (Array.isArray(filter.ignoredBy)) {
|
|
1257
|
+
for (const id of filter.ignoredBy) if (isName(id) && !ids.has(id)) errors.push(`${context}: gráfico "${id}" de \`ignoredBy\` não existe`);
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
if (layout.parameters !== void 0) {
|
|
1262
|
+
if (!Array.isArray(layout.parameters)) return [...errors, "`parameters` precisa ser uma lista"];
|
|
1263
|
+
const names = /* @__PURE__ */ new Set();
|
|
1264
|
+
layout.parameters.forEach((parameter, index) => {
|
|
1265
|
+
const context = `parâmetro ${index + 1}`;
|
|
1266
|
+
if (!isRecord(parameter)) {
|
|
1267
|
+
errors.push(`${context} inválido`);
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (typeof parameter.name !== "string" || !LAYOUT_ID.test(parameter.name)) errors.push(`${context}: \`name\` inválido`);
|
|
1271
|
+
else if (names.has(parameter.name)) errors.push(`${context}: name repetido "${parameter.name}"`);
|
|
1272
|
+
else names.add(parameter.name);
|
|
1273
|
+
if (parameter.label !== void 0 && typeof parameter.label !== "string") errors.push(`${context}: \`label\` precisa ser texto`);
|
|
1274
|
+
if (!Array.isArray(parameter.measures) || !parameter.measures.every(isName)) errors.push(`${context}: \`measures\` precisa ser uma lista de medidas`);
|
|
1275
|
+
else if (!parameter.measures.length) errors.push(`${context}: \`measures\` precisa de pelo menos uma medida`);
|
|
1276
|
+
else if (new Set(parameter.measures).size !== parameter.measures.length) errors.push(`${context}: \`measures\` tem medida repetida`);
|
|
1277
|
+
if (parameter.default !== void 0 && (!isName(parameter.default) || Array.isArray(parameter.measures) && !parameter.measures.includes(parameter.default))) errors.push(`${context}: \`default\` precisa ser uma das medidas`);
|
|
1278
|
+
if (parameter.width !== void 0 && typeof parameter.width !== "number") errors.push(`${context}: \`width\` precisa ser um número`);
|
|
1134
1279
|
});
|
|
1135
1280
|
}
|
|
1136
1281
|
return errors;
|
|
@@ -1161,6 +1306,7 @@ function fieldReferences(fields, item) {
|
|
|
1161
1306
|
if (value === void 0 || value === "") continue;
|
|
1162
1307
|
if (spec.kind === "dimension") refs.dimensions.push(value);
|
|
1163
1308
|
if (spec.kind === "dimensions") refs.dimensions.push(...value);
|
|
1309
|
+
if (spec.kind === "filters") refs.dimensions.push(...value);
|
|
1164
1310
|
if (spec.kind === "measure") refs.measures.push(value);
|
|
1165
1311
|
if (spec.kind === "measures") refs.measures.push(...value);
|
|
1166
1312
|
if (spec.kind === "formatter") refs.formatters.push(value);
|
|
@@ -1255,6 +1401,12 @@ function layoutModelWarnings(layout, model, types = BUILTIN_CHART_TYPES) {
|
|
|
1255
1401
|
}
|
|
1256
1402
|
}
|
|
1257
1403
|
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`);
|
|
1404
|
+
for (const parameter of layout.parameters ?? []) for (const measure of parameter.measures ?? []) if (!model.measures[measure]) warnings.push(`parâmetro "${parameter.label || parameter.name}": medida "${measure}" não existe`);
|
|
1405
|
+
const parameterNames = new Set((layout.parameters ?? []).map((parameter) => parameter.name));
|
|
1406
|
+
for (const chart of layout.charts) {
|
|
1407
|
+
const linked = chart.measureParameter;
|
|
1408
|
+
if (typeof linked === "string" && linked && !parameterNames.has(linked)) warnings.push(`"${chart.title || chart.id}": parâmetro de medida "${linked}" não existe`);
|
|
1409
|
+
}
|
|
1258
1410
|
return warnings.filter(Boolean);
|
|
1259
1411
|
}
|
|
1260
1412
|
const isModelField = (model, name) => Object.hasOwn(model.dimensions, name) || Object.hasOwn(model.measures, name);
|
|
@@ -1355,6 +1507,6 @@ function layoutFileContent(layout) {
|
|
|
1355
1507
|
return `${JSON.stringify(layout, null, 2)}\n`;
|
|
1356
1508
|
}
|
|
1357
1509
|
//#endregion
|
|
1358
|
-
export {
|
|
1510
|
+
export { colorScaleStyles as A, COLUMN_VERTICAL_ALIGNS as C, RULE_OPERATOR_LABEL as D, RULE_OPERATORS as E, matchesValueRule as M, rowRuleStyles as N, UNARY_OPERATORS as O, COLUMN_ALIGNS as S, NUMERIC_OPERATORS as T, percentColumn as _, FILTER_FIELDS as a, validateLayout as b, chartCardStyle as c, chartReferences as d, customChartProps as f, layoutModelWarnings as g, layoutFileContent as h, CARD_FIELDS as i, contrastText as j, VALUE_RULE_OPERATORS as k, chartColumns as l, isBuiltinChart as m, CANVAS_PRESETS as n, PARAMETER_FIELDS as o, filterReferences as p, CARDS_FIELDS as r, cardsGap as s, BUILTIN_CHART_TYPES as t, chartOfType as u, resolveChartTypes as v, DEFAULT_SCALE_MIN_COLOR as w, COLOR_SCALE_TARGETS as x, tableColumns as y };
|
|
1359
1511
|
|
|
1360
|
-
//# sourceMappingURL=layout-
|
|
1512
|
+
//# sourceMappingURL=layout-BuZhfoRI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-BuZhfoRI.js","names":["isRecord"],"sources":["../src/formatRules.ts","../src/layout.ts"],"sourcesContent":["/**\r\n * Formatação condicional da tabela: regras que mudam a cor do texto, o fundo, o negrito e o\r\n * alinhamento (horizontal e vertical) de uma célula ou da linha inteira conforme o valor de uma dimensão/medida da linha (como a \"formatação\r\n * condicional\" do Power BI). Usado pelo `DataTable`, pela validação do layout e pelo devtools.\r\n *\r\n * ```json\r\n * \"formatRules\": [\r\n * { \"field\": \"custoTon\", \"operator\": \">\", \"value\": 100, \"color\": \"#c62828\" },\r\n * { \"field\": \"margem\", \"operator\": \"<\", \"value\": 0, \"target\": \"row\", \"background\": \"#ffebee\" }\r\n * ]\r\n * ```\r\n */\r\n\r\nimport type { DashboardRow } from './types'\r\n\r\nexport const COLUMN_ALIGNS = ['left', 'center', 'right'] as const\r\nexport type ColumnAlign = (typeof COLUMN_ALIGNS)[number]\r\nexport const COLUMN_VERTICAL_ALIGNS = ['top', 'middle', 'bottom'] as const\r\nexport type ColumnVerticalAlign = (typeof COLUMN_VERTICAL_ALIGNS)[number]\r\n\r\nexport const RULE_OPERATORS = [\r\n '>',\r\n '>=',\r\n '<',\r\n '<=',\r\n '=',\r\n '!=',\r\n 'between',\r\n 'contains',\r\n 'empty',\r\n 'notEmpty'\r\n] as const\r\nexport type RuleOperator = (typeof RULE_OPERATORS)[number]\r\n\r\nexport const RULE_OPERATOR_LABEL: Record<RuleOperator, string> = {\r\n '>': 'maior que',\r\n '>=': 'maior ou igual a',\r\n '<': 'menor que',\r\n '<=': 'menor ou igual a',\r\n '=': 'igual a',\r\n '!=': 'diferente de',\r\n between: 'entre',\r\n contains: 'contém',\r\n empty: 'vazio',\r\n notEmpty: 'não vazio'\r\n}\r\n\r\n/** Operadores que comparam com número (`value`, e `to` no `between`). */\r\nexport const NUMERIC_OPERATORS: readonly RuleOperator[] = ['>', '>=', '<', '<=', 'between']\r\n/** Operadores sem valor. */\r\nexport const UNARY_OPERATORS: readonly RuleOperator[] = ['empty', 'notEmpty']\r\n\r\nexport type FormatRule = {\r\n /**\r\n * Dimensão ou medida avaliada. Pode estar fora da tabela: entra na consulta sem coluna (uma\r\n * dimensão de fora divide as linhas se tiver mais de um valor por linha).\r\n */\r\n field: string\r\n operator: RuleOperator\r\n /** Valor comparado com o valor bruto (percentual como fração: 10% = 0.1). */\r\n value?: number | string\r\n /** Fim do intervalo do `between` (inclusivo, como o `value`). */\r\n to?: number\r\n /** `cell` (padrão) pinta uma célula; `row`, a linha inteira. */\r\n target?: 'cell' | 'row'\r\n /** Coluna pintada quando `target` é `cell`. Padrão: o próprio `field`. */\r\n column?: string\r\n /** Cor do texto. */\r\n color?: string\r\n /** Cor de fundo. */\r\n background?: string\r\n bold?: boolean\r\n /** Alinhamento horizontal da célula (ou das células da linha). */\r\n align?: ColumnAlign\r\n /** Alinhamento vertical da célula (ou das células da linha). */\r\n verticalAlign?: ColumnVerticalAlign\r\n}\r\n\r\nexport type RuleStyle = Pick<\r\n FormatRule,\r\n 'color' | 'background' | 'bold' | 'align' | 'verticalAlign'\r\n>\r\n\r\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\r\n typeof value === 'object' && value !== null && !Array.isArray(value)\r\nconst isColor = (value: unknown) => typeof value === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(value)\r\nconst RULE_KEYS = [\r\n 'field',\r\n 'operator',\r\n 'value',\r\n 'to',\r\n 'target',\r\n 'column',\r\n 'color',\r\n 'background',\r\n 'bold',\r\n 'align',\r\n 'verticalAlign'\r\n]\r\n\r\n/** Problema de forma de uma regra (`null` = válida). */\r\nexport function formatRuleError(rule: unknown): string | null {\r\n if (!isRecord(rule)) return 'precisa ser um objeto'\r\n const unknown = Object.keys(rule).find((key) => !RULE_KEYS.includes(key))\r\n if (unknown) return `campo desconhecido \\`${unknown}\\``\r\n if (typeof rule.field !== 'string' || !rule.field) return '`field` é obrigatório'\r\n if (!RULE_OPERATORS.includes(rule.operator as RuleOperator))\r\n return `\\`operator\\` precisa ser ${RULE_OPERATORS.join(', ')}`\r\n const operator = rule.operator as RuleOperator\r\n if (NUMERIC_OPERATORS.includes(operator)) {\r\n if (typeof rule.value !== 'number' || !Number.isFinite(rule.value))\r\n return '`value` precisa ser um número'\r\n if (operator === 'between' && (typeof rule.to !== 'number' || !Number.isFinite(rule.to)))\r\n return '`to` precisa ser um número (fim do intervalo)'\r\n } else if (!UNARY_OPERATORS.includes(operator)) {\r\n if (typeof rule.value !== 'number' && typeof rule.value !== 'string')\r\n return '`value` precisa ser texto ou número'\r\n }\r\n if (operator !== 'between' && rule.to !== undefined) return '`to` só vale com `between`'\r\n if (rule.target !== undefined && rule.target !== 'cell' && rule.target !== 'row')\r\n return '`target` precisa ser cell ou row'\r\n if (rule.column !== undefined && (typeof rule.column !== 'string' || !rule.column))\r\n return '`column` precisa ser texto'\r\n if (rule.target === 'row' && rule.column !== undefined) return '`column` não vale com row'\r\n for (const key of ['color', 'background'])\r\n if (rule[key] !== undefined && !isColor(rule[key]))\r\n return `\\`${key}\\` precisa ser uma cor (#rrggbb)`\r\n if (rule.bold !== undefined && typeof rule.bold !== 'boolean')\r\n return '`bold` precisa ser true/false'\r\n if (rule.align !== undefined && !COLUMN_ALIGNS.includes(rule.align as ColumnAlign))\r\n return `\\`align\\` precisa ser ${COLUMN_ALIGNS.join(', ')}`\r\n if (\r\n rule.verticalAlign !== undefined &&\r\n !COLUMN_VERTICAL_ALIGNS.includes(rule.verticalAlign as ColumnVerticalAlign)\r\n )\r\n return `\\`verticalAlign\\` precisa ser ${COLUMN_VERTICAL_ALIGNS.join(', ')}`\r\n if (\r\n rule.color === undefined &&\r\n rule.background === undefined &&\r\n !rule.bold &&\r\n rule.align === undefined &&\r\n rule.verticalAlign === undefined\r\n )\r\n return 'precisa de `color`, `background`, `bold`, `align` ou `verticalAlign`'\r\n return null\r\n}\r\n\r\n/**\r\n * Regra de um valor só (sem campo nem estilo), ex.: mostrar o rótulo da barra só quando o valor é\r\n * diferente de zero: `{ \"operator\": \"!=\", \"value\": 0 }`.\r\n */\r\nexport type ValueRule = { operator: ValueRuleOperator; value: number; to?: number }\r\n\r\nexport const VALUE_RULE_OPERATORS = ['>', '>=', '<', '<=', '=', '!=', 'between'] as const\r\nexport type ValueRuleOperator = (typeof VALUE_RULE_OPERATORS)[number]\r\n\r\n/** Problema de forma de uma `ValueRule` (`null` = válida). */\r\nexport function valueRuleError(rule: unknown): string | null {\r\n if (!isRecord(rule)) return 'precisa ser { operator, value }'\r\n const unknown = Object.keys(rule).find((key) => !['operator', 'value', 'to'].includes(key))\r\n if (unknown) return `campo desconhecido \\`${unknown}\\``\r\n if (!VALUE_RULE_OPERATORS.includes(rule.operator as ValueRuleOperator))\r\n return `\\`operator\\` precisa ser ${VALUE_RULE_OPERATORS.join(', ')}`\r\n if (typeof rule.value !== 'number' || !Number.isFinite(rule.value))\r\n return '`value` precisa ser um número'\r\n if (rule.operator === 'between') {\r\n if (typeof rule.to !== 'number' || !Number.isFinite(rule.to))\r\n return '`to` precisa ser um número (fim do intervalo)'\r\n } else if (rule.to !== undefined) return '`to` só vale com `between`'\r\n return null\r\n}\r\n\r\n/** O valor atende à regra? Sem regra, sempre. */\r\nexport const matchesValueRule = (rule: ValueRule | undefined, value: number) =>\r\n !rule || matchesRule({ field: '', ...rule }, value)\r\n\r\nconst isEmpty = (value: unknown) => value === null || value === undefined || value === ''\r\n\r\n/** A regra vale para esse valor? Valor vazio só casa com `empty` (e `!=`). */\r\nexport function matchesRule(rule: FormatRule, value: DashboardRow[string] | undefined): boolean {\r\n if (rule.operator === 'empty') return isEmpty(value)\r\n if (rule.operator === 'notEmpty') return !isEmpty(value)\r\n if (isEmpty(value)) return rule.operator === '!='\r\n const number = typeof value === 'number' ? value : Number(value)\r\n const target = rule.value\r\n switch (rule.operator) {\r\n case '>':\r\n return number > Number(target)\r\n case '>=':\r\n return number >= Number(target)\r\n case '<':\r\n return number < Number(target)\r\n case '<=':\r\n return number <= Number(target)\r\n case 'between': {\r\n const [low, high] = [Number(target), Number(rule.to)].sort((a, b) => a - b)\r\n return number >= (low as number) && number <= (high as number)\r\n }\r\n case '=':\r\n case '!=': {\r\n // Número com número compara o valor (1 = \"1.0\"); o resto, o texto sem caixa\r\n const equal =\r\n typeof target === 'number' || (Number.isFinite(number) && typeof value === 'number')\r\n ? number === Number(target)\r\n : String(value).toLowerCase() === String(target).toLowerCase()\r\n return rule.operator === '=' ? equal : !equal\r\n }\r\n case 'contains':\r\n return String(value)\r\n .toLowerCase()\r\n .includes(String(target ?? '').toLowerCase())\r\n }\r\n}\r\n\r\n/**\r\n * Estilo de cada coluna de uma linha: as regras `row` valem para todas, as `cell` só para a coluna\r\n * delas, por cima das de linha. Entre regras do mesmo alcance, a de baixo ganha.\r\n */\r\nexport function rowRuleStyles(\r\n rules: FormatRule[],\r\n row: DashboardRow,\r\n columns: string[]\r\n): Record<string, RuleStyle> {\r\n const rowStyle: RuleStyle = {}\r\n const cells: Record<string, RuleStyle> = {}\r\n for (const rule of rules) {\r\n if (!matchesRule(rule, row[rule.field])) continue\r\n const style = ruleStyle(rule)\r\n if (rule.target === 'row') Object.assign(rowStyle, style)\r\n else {\r\n const column = rule.column ?? rule.field\r\n cells[column] = { ...cells[column], ...style }\r\n }\r\n }\r\n return Object.fromEntries(columns.map((column) => [column, { ...rowStyle, ...cells[column] }]))\r\n}\r\n\r\nexport function ruleStyle(rule: FormatRule): RuleStyle {\r\n return {\r\n ...(rule.color && { color: rule.color }),\r\n ...(rule.background && { background: rule.background }),\r\n ...(rule.bold && { bold: true }),\r\n ...(rule.align && { align: rule.align }),\r\n ...(rule.verticalAlign && { verticalAlign: rule.verticalAlign })\r\n }\r\n}\r\n\r\n// --- escala de cor (gradiente) ---\r\n\r\nexport const COLOR_SCALE_TARGETS = ['background', 'text'] as const\r\nexport type ColorScaleTarget = (typeof COLOR_SCALE_TARGETS)[number]\r\n\r\n/**\r\n * Escala de cor de uma coluna numérica (a \"escala de cores\" do Power BI): a cor da célula vai de\r\n * `minColor` (no `min`) a `color` (no `max`) conforme o valor. Ex.: uma coluna de 0 a 100 com\r\n * `{ \"column\": \"nota\", \"min\": 0, \"max\": 100, \"color\": \"#2e7d32\" }` fica branca no 0 e verde no 100.\r\n */\r\nexport type ColorScale = {\r\n /** Coluna pintada e avaliada: medida, dimensão numérica ou `%:medida`. */\r\n column: string\r\n /** Cor no máximo. */\r\n color: string\r\n /** Cor no mínimo. Padrão: branco. */\r\n minColor?: string\r\n /** Valor da `minColor` (abaixo dele, a mesma cor). Padrão: o menor valor da coluna na tabela. */\r\n min?: number\r\n /** Valor da `color` (acima dele, a mesma cor). Padrão: o maior valor da coluna na tabela. */\r\n max?: number\r\n /** `background` (padrão) pinta o fundo; `text`, o texto. */\r\n target?: ColorScaleTarget\r\n}\r\n\r\nexport const DEFAULT_SCALE_MIN_COLOR = '#ffffff'\r\n\r\nconst SCALE_KEYS = ['column', 'color', 'minColor', 'min', 'max', 'target']\r\n\r\n/** Problema de forma de uma escala (`null` = válida). */\r\nexport function colorScaleError(scale: unknown): string | null {\r\n if (!isRecord(scale)) return 'precisa ser um objeto'\r\n const unknown = Object.keys(scale).find((key) => !SCALE_KEYS.includes(key))\r\n if (unknown) return `campo desconhecido \\`${unknown}\\``\r\n if (typeof scale.column !== 'string' || !scale.column) return '`column` é obrigatório'\r\n if (!isColor(scale.color)) return '`color` precisa ser uma cor (#rrggbb)'\r\n if (scale.minColor !== undefined && !isColor(scale.minColor))\r\n return '`minColor` precisa ser uma cor (#rrggbb)'\r\n for (const key of ['min', 'max'])\r\n if (\r\n scale[key] !== undefined &&\r\n (typeof scale[key] !== 'number' || !Number.isFinite(scale[key]))\r\n )\r\n return `\\`${key}\\` precisa ser um número`\r\n if (typeof scale.min === 'number' && typeof scale.max === 'number' && scale.min >= scale.max)\r\n return '`min` precisa ser menor que `max`'\r\n if (scale.target !== undefined && !COLOR_SCALE_TARGETS.includes(scale.target as ColorScaleTarget))\r\n return `\\`target\\` precisa ser ${COLOR_SCALE_TARGETS.join(' ou ')}`\r\n return null\r\n}\r\n\r\nconst numeric = (value: DashboardRow[string] | undefined) => {\r\n if (isEmpty(value)) return null\r\n const number = typeof value === 'number' ? value : Number(value)\r\n return Number.isFinite(number) ? number : null\r\n}\r\n\r\n/** `[r, g, b, a]` de `#rgb`, `#rgba`, `#rrggbb` ou `#rrggbbaa`. */\r\nfunction parseColor(color: string): [number, number, number, number] {\r\n let hex = color.slice(1)\r\n if (hex.length <= 4) hex = hex.replace(/./g, '$&$&')\r\n const channel = (index: number) => Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16)\r\n return [channel(0), channel(1), channel(2), hex.length === 8 ? channel(3) / 255 : 1]\r\n}\r\n\r\nfunction mixColors(from: string, to: string, t: number) {\r\n const [a, b] = [parseColor(from), parseColor(to)]\r\n const [r, g, bl, alpha] = a.map((value, i) => value + ((b[i] as number) - value) * t) as [\r\n number,\r\n number,\r\n number,\r\n number\r\n ]\r\n const rgb = [r, g, bl].map(Math.round).join(', ')\r\n return alpha >= 1 ? `rgb(${rgb})` : `rgba(${rgb}, ${Number(alpha.toFixed(3))})`\r\n}\r\n\r\n/**\r\n * Texto legível sobre um fundo da escala (`rgb(...)`/`rgba(...)`): branco nos escuros, `undefined`\r\n * (o padrão) nos claros.\r\n */\r\nexport function contrastText(background: string) {\r\n const [r, g, b, alpha = 1] = (background.match(/[\\d.]+/g) ?? []).map(Number) as number[]\r\n const luminance = (0.299 * (r ?? 255) + 0.587 * (g ?? 255) + 0.114 * (b ?? 255)) / 255\r\n // Fundo transparente mistura com o branco da tabela\r\n return 1 - alpha * (1 - luminance) < 0.55 ? '#ffffff' : undefined\r\n}\r\n\r\n/**\r\n * Estilo de cada coluna com escala, por linha: `min`/`max` ausentes saem dos valores da coluna em\r\n * `rows` (as linhas mostradas). Linha sem valor fica sem cor. O texto sobre fundo escuro fica com\r\n * quem junta com as regras (`contrastText`).\r\n */\r\nexport function colorScaleStyles(\r\n scales: ColorScale[],\r\n rows: DashboardRow[]\r\n): (row: DashboardRow) => Record<string, RuleStyle> {\r\n const ranges = scales.map((scale) => {\r\n const values = rows.map((row) => numeric(row[scale.column])).filter((v) => v !== null)\r\n return [scale.min ?? Math.min(...values), scale.max ?? Math.max(...values)] as const\r\n })\r\n return (row) => {\r\n const styles: Record<string, RuleStyle> = {}\r\n scales.forEach((scale, index) => {\r\n const value = numeric(row[scale.column])\r\n const [min, max] = ranges[index] as readonly [number, number]\r\n if (value === null || !Number.isFinite(min) || !Number.isFinite(max)) return\r\n // Coluna com um valor só (min = max): cor cheia\r\n const t = max > min ? Math.min(1, Math.max(0, (value - min) / (max - min))) : 1\r\n const color = mixColors(scale.minColor ?? DEFAULT_SCALE_MIN_COLOR, scale.color, t)\r\n styles[scale.column] = {\r\n ...styles[scale.column],\r\n [scale.target === 'text' ? 'color' : 'background']: color\r\n }\r\n })\r\n return styles\r\n }\r\n}\r\n","/**\r\n * Layout de gráficos de uma página: lista declarativa (JSON) que o `<DashboardCharts>` renderiza. É\r\n * JSON — e não JSX — para o devtools conseguir adicionar e editar gráficos pelo navegador e gravar o\r\n * arquivo de volta. Fica fora de `server/` porque o client e o servidor validam o mesmo formato.\r\n *\r\n * Os campos de cada tipo são descritos em `CHART_FIELDS`: é de lá que saem a validação, os avisos\r\n * de dimensão/medida inexistente e o formulário da aba Layout do devtools. O app registra tipos\r\n * próprios (`ChartTypeSpec`) no `<DashboardProvider charts>` e no `devtools.chartTypes` do servidor;\r\n * as funções daqui recebem o registro completo (`resolveChartTypes`).\r\n */\r\n\r\nimport {\r\n type ColorScale,\r\n colorScaleError,\r\n COLUMN_ALIGNS,\r\n COLUMN_VERTICAL_ALIGNS,\r\n type ColumnAlign,\r\n type ColumnVerticalAlign,\r\n type FormatRule,\r\n formatRuleError,\r\n type ValueRule,\r\n valueRuleError\r\n} from './formatRules'\r\nimport { isAggregate, type ModelDef, VALUE_FORMATS, type ValueFormat } from './model'\r\n\r\nexport const CHART_TYPES = ['kpi', 'combo', 'bar', 'donut', 'table'] as const\r\nexport type ChartType = (typeof CHART_TYPES)[number]\r\n\r\nexport const CHART_TYPE_LABEL: Record<ChartType, string> = {\r\n kpi: 'KPI (card)',\r\n combo: 'Combo (barras + linha)',\r\n bar: 'Barras',\r\n donut: 'Rosca',\r\n table: 'Tabela'\r\n}\r\n\r\n/** Colunas do grid em telas largas; cada gráfico ocupa `span` delas (no celular, a linha toda). */\r\nexport const LAYOUT_COLUMNS = 12\r\n\r\ntype BaseChartDef = {\r\n /** Identificador estável do gráfico dentro do layout (letras, números, \"-\" e \"_\"). */\r\n id: string\r\n title: string\r\n /** Colunas ocupadas (1 a 12). Padrão: 12. */\r\n span?: number\r\n height?: number\r\n /** Aparência do card deste gráfico (sobrepõe o `cards` do layout). */\r\n card?: CardStyle\r\n /**\r\n * Dimensões cujos filtros este gráfico NÃO respeita: slicers do painel ou o filtro cruzado de um\r\n * clique em outro gráfico (como o \"Editar interações\" do Power BI). Vazio/ausente = respeita todos.\r\n * Ex.: numa rosca por produto com `clickToFilter`, pôr `[\"descricaoProduto\"]` nas tabelas que devem\r\n * continuar mostrando o total, sem reagir ao clique na fatia.\r\n */\r\n ignoreFilters?: string[]\r\n}\r\n\r\n/**\r\n * Formato de uma medida num gráfico: sobrepõe o `format`/`decimals` do dicionário do modelo só\r\n * naquele gráfico.\r\n */\r\nexport type ChartNumberFormat = {\r\n format?: ValueFormat\r\n /** Casas decimais (0 a 10). */\r\n decimals?: number\r\n /** Notação curta: 1,2 mil / 3,4 mi. */\r\n compact?: boolean\r\n /** Texto antes do número (ex.: \"US$ \"). */\r\n prefix?: string\r\n /** Texto depois do número (ex.: \" t\"). */\r\n suffix?: string\r\n}\r\n\r\nexport {\r\n type ColorScale,\r\n COLUMN_ALIGNS,\r\n COLUMN_VERTICAL_ALIGNS,\r\n type ColumnAlign,\r\n type ColumnVerticalAlign\r\n} from './formatRules'\r\n\r\n/** Rótulos e formatos do gráfico, por nome de dimensão/medida (sobrepõem o dicionário). */\r\ntype ColumnsDisplay = {\r\n /** `{ dimensãoOuMedida: 'Rótulo' }`: legenda, tooltip e cabeçalho. */\r\n labels?: Record<string, string>\r\n /** `{ medida: { decimals: 1, ... } }`. */\r\n formats?: Record<string, ChartNumberFormat>\r\n}\r\n\r\nexport type ComboChartDef = BaseChartDef &\r\n ColumnsDisplay & {\r\n type: 'combo'\r\n dimension: string\r\n bar: string\r\n line: string\r\n detail?: string\r\n /** Nome de um formatter passado ao `<DashboardCharts formatters>` (funções não cabem no JSON). */\r\n formatDimension?: string\r\n clickToFilter?: boolean\r\n hideEmpty?: boolean\r\n maxLabels?: number\r\n barColor?: string\r\n lineColor?: string\r\n }\r\n\r\nexport type BarChartDef = BaseChartDef &\r\n ColumnsDisplay & {\r\n type: 'bar'\r\n dimension: string\r\n /** Uma série por medida (ou, com `series`, só a primeira medida). */\r\n measures: string[]\r\n /**\r\n * Dimensão da legenda: quebra a medida pelos valores dela, uma série por valor (a \"Legenda\" do\r\n * Power BI). Usa só a primeira medida.\r\n */\r\n series?: string\r\n formatSeries?: string\r\n /** Séries além dessa quantidade viram \"Outros\" (com `series`). */\r\n maxSeries?: number\r\n orientation?: 'vertical' | 'horizontal'\r\n stacked?: boolean\r\n /**\r\n * `dimension` = ordem da dimensão; `desc`/`asc` = pela primeira medida (com `series`, pelo\r\n * total da barra).\r\n */\r\n sort?: 'dimension' | 'desc' | 'asc'\r\n /** Só os N primeiros depois de ordenar. */\r\n limit?: number\r\n formatDimension?: string\r\n clickToFilter?: boolean\r\n hideEmpty?: boolean\r\n /** Todos os valores da dimensão no eixo, com ou sem dado (sobrepõe `hideEmpty`). */\r\n showEmpty?: boolean\r\n showValues?: boolean\r\n /** Onde fica o valor de cada barra. Padrão: `outside` (na ponta, fora da barra). */\r\n valuePosition?: BarValuePosition\r\n /** Cor do valor. Padrão: escuro fora da barra, branco dentro. */\r\n valueColor?: string\r\n /** Tamanho do valor, em px. Padrão: 12. */\r\n valueSize?: number\r\n /** Empilhado: total da pilha na ponta. */\r\n showTotal?: boolean\r\n /** Só mostra o valor (e o total) que atende à regra, ex.: `{ operator: '!=', value: 0 }`. */\r\n valueRule?: ValueRule\r\n /**\r\n * Com `clickToFilter`, o filtro da própria dimensão (e da série) esconde o resto em vez de só\r\n * apagá-lo. Padrão: `false` (apaga).\r\n */\r\n hideUnselected?: boolean\r\n /**\r\n * Cor por série: `{ medida: '#rrggbb' }` ou, com `series`, `{ valor: '#rrggbb' }` (também\r\n * \"(em branco)\" e \"Outros\"). O que faltar segue a paleta.\r\n */\r\n colors?: Record<string, string>\r\n }\r\n\r\nexport const BAR_VALUE_POSITIONS = ['outside', 'insideEnd', 'center', 'insideBase'] as const\r\nexport type BarValuePosition = (typeof BAR_VALUE_POSITIONS)[number]\r\n\r\nexport type DonutChartDef = BaseChartDef &\r\n ColumnsDisplay & {\r\n type: 'donut'\r\n dimension: string\r\n measure: string\r\n /** Nome de um parâmetro de medida do layout: a medida passa a seguir o slicer dele. */\r\n measureParameter?: string\r\n /** Fatias além dessa quantidade viram \"Outros\". */\r\n maxSlices?: number\r\n /** Casas decimais do percentual. Padrão: 1. */\r\n percentDecimals?: number\r\n formatDimension?: string\r\n clickToFilter?: boolean\r\n /** Lado da legenda. Padrão: `right` (com nome, % e valor; em cima/embaixo, só o nome). */\r\n legendPosition?: DonutLegendPosition\r\n /** Valor de cada fatia do lado de fora da rosca. */\r\n showValues?: boolean\r\n /** O que o valor de fora mostra. Padrão: `both` (`44.981 (51%)`). */\r\n valueContent?: DonutValueContent\r\n /** Total no furo da rosca. Padrão: `true`. */\r\n showTotal?: boolean\r\n }\r\n\r\nexport const DONUT_LEGEND_POSITIONS = ['right', 'left', 'top', 'bottom'] as const\r\nexport type DonutLegendPosition = (typeof DONUT_LEGEND_POSITIONS)[number]\r\nexport const DONUT_VALUE_CONTENTS = ['both', 'value', 'percent'] as const\r\nexport type DonutValueContent = (typeof DONUT_VALUE_CONTENTS)[number]\r\n\r\nexport type TableChartDef = BaseChartDef &\r\n ColumnsDisplay & {\r\n type: 'table'\r\n dimensions?: string[]\r\n measures: string[]\r\n /**\r\n * Medidas com coluna de participação no total: valor da linha ÷ soma da coluna. Medida que não\r\n * está em `measures` é consultada só para o percentual.\r\n */\r\n percentOf?: string[]\r\n /** Casas decimais das colunas de percentual. Padrão: 1. */\r\n percentDecimals?: number\r\n /**\r\n * Ordem das colunas misturando dimensões, medidas e percentuais (`%:medida`), ex.: uma medida\r\n * antes de uma dimensão. Ausente = dimensões e depois medidas; percentual fora da ordem vem logo\r\n * depois da medida dele (ou no fim, se a medida não é coluna).\r\n */\r\n columnOrder?: string[]\r\n /** Formatter por dimensão (`{ mes: 'nomeMes' }`). */\r\n formatDimensions?: Record<string, string>\r\n /** Linha de total, consultada sem as dimensões (razões saem certas, não somadas). */\r\n showTotal?: boolean\r\n /**\r\n * Dimensão ou medida da ordem inicial; o usuário reordena clicando no cabeçalho. Pode estar fora\r\n * da tabela (ex.: número do mês para ordenar o nome do mês): entra na consulta sem coluna.\r\n */\r\n sortBy?: string\r\n sortDirection?: 'asc' | 'desc'\r\n clickToFilter?: boolean\r\n /** Formatação condicional: cor do texto/fundo da célula ou da linha conforme uma regra. */\r\n formatRules?: FormatRule[]\r\n /** Escalas de cor (gradiente): fundo/texto da coluna de `minColor` a `color` conforme o valor. */\r\n colorScales?: ColorScale[]\r\n /** Bordas entre as células (grade). Padrão: só a linha embaixo de cada linha. */\r\n cellBorders?: boolean\r\n /** Cor das bordas da grade. */\r\n borderColor?: string\r\n /**\r\n * Alinhamento horizontal por coluna (`{ coluna: 'center' }`, `%:medida` para o percentual), no\r\n * cabeçalho, nas células e no total. Padrão: dimensão à esquerda, número à direita.\r\n */\r\n align?: Record<string, ColumnAlign>\r\n /** Alinhamento vertical por coluna (`top`, `middle`, `bottom`). Padrão: `middle`. */\r\n verticalAlign?: Record<string, ColumnVerticalAlign>\r\n /** Espaçamento vertical das células (px), em cima e embaixo. Padrão: 8. */\r\n cellPadding?: number\r\n /** Tamanho da fonte das células e do cabeçalho (px). Padrão: 13. */\r\n fontSize?: number\r\n }\r\n\r\nexport type KpiChartDef = Omit<BaseChartDef, 'height'> &\r\n ColumnsDisplay & {\r\n type: 'kpi'\r\n /** Medida do número grande (total com os filtros da página). */\r\n measure: string\r\n /** Nome de um parâmetro de medida do layout: a medida passa a seguir o slicer dele. */\r\n measureParameter?: string\r\n /** Medida extra numa linha menor embaixo (ex.: volume, custo/t). */\r\n secondary?: string\r\n }\r\n\r\n/** Gráficos da lib (`type` em `CHART_TYPES`). */\r\nexport type BuiltinChartDef =\r\n | KpiChartDef\r\n | ComboChartDef\r\n | BarChartDef\r\n | DonutChartDef\r\n | TableChartDef\r\n\r\n/** Gráfico de um tipo registrado pelo app: os campos vêm do `ChartTypeSpec` dele. */\r\nexport type CustomChartDef = BaseChartDef & { type: string; [field: string]: unknown }\r\n\r\nexport type ChartDef = BuiltinChartDef | CustomChartDef\r\n\r\nexport const isBuiltinChart = (chart: ChartDef): chart is BuiltinChartDef =>\r\n (CHART_TYPES as readonly string[]).includes(chart.type)\r\n\r\n/** Filtro (slicer) da página: múltipla escolha de uma dimensão, com filtro cruzado. */\r\nexport type FilterDef = {\r\n dimension: string\r\n /** Padrão: `label` da dimensão no dicionário. */\r\n label?: string\r\n width?: number\r\n /** Nome de um formatter do `<DashboardFilters formatters>` para as opções. */\r\n formatLabel?: string\r\n /**\r\n * Ids dos gráficos que NÃO reagem a este filtro (o \"Editar interações\" do Power BI, pela ótica do\r\n * slicer). Ausente/vazio = todos reagem. Como os filtros são guardados por dimensão, listar um\r\n * gráfico aqui equivale a pôr a dimensão deste filtro no `ignoreFilters` dele.\r\n */\r\n ignoredBy?: string[]\r\n}\r\n\r\n/**\r\n * Parâmetro de medida (\"medida dinâmica\"): um slicer que escolhe UMA medida entre `measures`. Um\r\n * gráfico de medida única (rosca, KPI) segue o parâmetro quando aponta para ele em\r\n * `measureParameter`. É o \"parâmetro de campo\" do Power BI, só para medidas. Renderizado pelo\r\n * `<DashboardFilters>`, junto dos filtros.\r\n */\r\nexport type ParameterDef = {\r\n /** Id do parâmetro no layout (letras, números, \"-\" e \"_\"). */\r\n name: string\r\n /** Rótulo do slicer. Padrão: \"Medida\". */\r\n label?: string\r\n /** Medidas oferecidas (a `default`, ou a primeira, é a inicial). */\r\n measures: string[]\r\n /** Medida inicial. Padrão: a primeira de `measures`. */\r\n default?: string\r\n width?: number\r\n}\r\n\r\n/**\r\n * Tela de tamanho fixo: o `<DashboardCanvas>` desenha o dashboard nessas dimensões e o escala para\r\n * caber na largura da tela, mantendo a proporção — os gráficos não se reorganizam (como o \"Ajustar à\r\n * largura\" do Power BI).\r\n */\r\nexport type CanvasDef = {\r\n /** Largura de projeto, em px (ex.: 1920). */\r\n width: number\r\n /** Altura mínima de projeto, em px (ex.: 1080); conteúdo mais alto estende a tela. */\r\n height?: number\r\n}\r\n\r\n/** Moldura de um gráfico (o card): ausente = padrão do componente. */\r\nexport type CardStyle = {\r\n background?: string\r\n borderColor?: string\r\n /** Espessura da borda, em px (0 = sem borda). */\r\n borderWidth?: number\r\n /** Arredondamento dos cantos, em px. */\r\n borderRadius?: number\r\n /** Sombra (elevation do MUI, 0 a 24). */\r\n shadow?: number\r\n /** Espaço interno, em px. */\r\n padding?: number\r\n titleColor?: string\r\n /** Tamanho do título, em px. */\r\n titleSize?: number\r\n titleAlign?: 'left' | 'center' | 'right'\r\n /** Linha divisória abaixo do título. Padrão: sem divisor. */\r\n titleDivider?: boolean\r\n /** Cor do divisor do título. Padrão: `#e0e0e0`. */\r\n titleDividerColor?: string\r\n /** Espaço entre o título e o divisor, em px (com divisor). Padrão: 4. */\r\n titleGap?: number\r\n /** Espaço entre o título/divisor e o conteúdo do gráfico, em px. Padrão: 8. */\r\n contentGap?: number\r\n}\r\n\r\n/** Cards de todos os gráficos do layout: estilo padrão e espaço entre eles. */\r\nexport type CardsStyle = CardStyle & {\r\n /** Espaço entre as linhas de cards, em px. */\r\n rowGap?: number\r\n /** Espaço entre as colunas de cards, em px. */\r\n columnGap?: number\r\n /** Espaço entre linhas e colunas, em px (formato antigo; `rowGap`/`columnGap` têm prioridade). */\r\n gap?: number\r\n}\r\n\r\nexport type ChartsLayout = {\r\n /** Nome do arquivo (`<layoutsDir>/<id>.json`) e chave do rascunho no devtools. */\r\n id: string\r\n /** Ausente = responsivo (12 colunas em telas largas, um gráfico por linha no celular). */\r\n canvas?: CanvasDef\r\n /** Estilo padrão dos cards e espaço entre eles; cada gráfico sobrepõe com `card`. */\r\n cards?: CardsStyle\r\n /** Renderizados pelo `<DashboardFilters>`, na ordem. */\r\n filters?: FilterDef[]\r\n /** Parâmetros de medida (slicers que trocam a medida de gráficos), no `<DashboardFilters>`. */\r\n parameters?: ParameterDef[]\r\n charts: ChartDef[]\r\n}\r\n\r\nexport const LAYOUT_ID = /^[A-Za-z0-9_-]{1,80}$/\r\n\r\n// --- campos por tipo ---\r\n\r\nexport type FieldSpec = {\r\n label: string\r\n hint?: string\r\n /** Agrupamento no formulário do devtools. */\r\n section:\r\n | 'Gráfico'\r\n | 'Dados'\r\n | 'Aparência'\r\n | 'Valores'\r\n | 'Comportamento'\r\n | 'Rótulos e formatos'\r\n | 'Formatação condicional'\r\n | 'Filtro'\r\n | 'Tela'\r\n | 'Card'\r\n required?: boolean\r\n /** Valor que o componente usa quando o campo não está no JSON. */\r\n default?: unknown\r\n} & (\r\n | { kind: 'text' }\r\n | { kind: 'color' }\r\n | { kind: 'boolean' }\r\n | { kind: 'integer'; min: number; max: number }\r\n | { kind: 'enum'; values: readonly string[]; labels?: Record<string, string> }\r\n /** Nome de dimensão/medida do modelo (uma ou uma lista). */\r\n | { kind: 'dimension' | 'measure' }\r\n | { kind: 'dimensions' }\r\n /** Lista de dimensões cujos filtros o gráfico ignora (`ignoreFilters`). */\r\n | { kind: 'filters' }\r\n /** Lista de ids de gráficos do layout (ex.: `ignoredBy` de um filtro). */\r\n | { kind: 'charts' }\r\n /** Nome de um parâmetro de medida do layout (ex.: `measureParameter` de uma rosca/KPI). */\r\n | { kind: 'parameter' }\r\n /**\r\n * `percent`: cada medida vira uma coluna de % do total (`%:medida`), que entra no `columnOrder`\r\n * como as outras colunas.\r\n */\r\n | { kind: 'measures'; percent?: boolean }\r\n /**\r\n * Nome de um formatter do `<DashboardCharts formatters>`. `of`: campo `dimension` que ele formata\r\n * (o devtools edita no ⚙ daquela dimensão); dispensável com uma dimensão e um formatter só.\r\n */\r\n | { kind: 'formatter'; of?: string }\r\n /** `{ dimensão: formatter }`. */\r\n | { kind: 'formatters' }\r\n /**\r\n * Uma das dimensões/medidas escolhidas no próprio gráfico. `outside`: também qualquer dimensão ou\r\n * medida do modelo (o componente consulta o que estiver fora do gráfico, sem mostrar).\r\n */\r\n | { kind: 'column'; outside?: boolean }\r\n /** `{ dimensãoOuMedida: rótulo }` das colunas do gráfico. */\r\n | { kind: 'labels' }\r\n /** `{ medida: ChartNumberFormat }` das medidas do gráfico. */\r\n | { kind: 'formats' }\r\n /** `{ coluna: 'left' | 'center' | 'right' }`: alinhamento horizontal de cada coluna. */\r\n | { kind: 'aligns' }\r\n /** `{ coluna: 'top' | 'middle' | 'bottom' }`: alinhamento vertical de cada coluna. */\r\n | { kind: 'verticalAligns' }\r\n /**\r\n * Ordem das colunas (dimensões e medidas misturadas). O devtools junta os campos `dimensions` e\r\n * `measures` da mesma seção numa lista só e grava a ordem aqui quando não é a padrão.\r\n */\r\n | { kind: 'columnOrder' }\r\n /** Regras de formatação condicional (`FormatRule[]`). */\r\n | { kind: 'formatRules' }\r\n /** Escalas de cor por coluna (`ColorScale[]`). */\r\n | { kind: 'colorScales' }\r\n /**\r\n * `{ série: '#rrggbb' }`: uma cor por medida do gráfico ou, quando o campo `dimension` em\r\n * `values` está preenchido, por valor dessa dimensão (o devtools não lista os valores; digita).\r\n */\r\n | { kind: 'colors'; values?: string }\r\n /** `ValueRule` (`{ operator, value, to? }`): ausente = sem regra (o devtools mostra uma checkbox). */\r\n | { kind: 'valueRule' }\r\n)\r\n\r\nconst common = (height: number): Record<string, FieldSpec> => ({\r\n title: { kind: 'text', label: 'Título', section: 'Gráfico' },\r\n span: {\r\n kind: 'integer',\r\n min: 1,\r\n max: LAYOUT_COLUMNS,\r\n default: LAYOUT_COLUMNS,\r\n label: 'Largura',\r\n hint: 'Colunas ocupadas em telas largas; no celular, cada gráfico ocupa a linha toda (menos com a tela de tamanho fixo)',\r\n section: 'Gráfico'\r\n },\r\n height: {\r\n kind: 'integer',\r\n min: 120,\r\n max: 2000,\r\n default: height,\r\n label: 'Altura (px)',\r\n section: 'Gráfico'\r\n }\r\n})\r\n\r\nconst formatDimension: FieldSpec = {\r\n kind: 'formatter',\r\n label: 'Formatar dimensão',\r\n hint: 'Formatters passados ao <DashboardCharts formatters={{ ... }}> da página',\r\n section: 'Dados'\r\n}\r\nconst clickToFilter: FieldSpec = {\r\n kind: 'boolean',\r\n default: false,\r\n label: 'Clique filtra o dashboard',\r\n hint: 'Ctrl/Cmd+clique acumula; clicar de novo no único selecionado limpa',\r\n section: 'Dados'\r\n}\r\nconst hideEmpty: FieldSpec = {\r\n kind: 'boolean',\r\n default: true,\r\n label: 'Esconder grupos vazios',\r\n section: 'Dados'\r\n}\r\n/** Filtros (por dimensão) que o gráfico não respeita; comum a todos os tipos. */\r\nconst ignoreFiltersField: FieldSpec = {\r\n kind: 'filters',\r\n label: 'Ignorar filtros',\r\n hint: 'Dimensões cujos filtros este gráfico não respeita (slicers do painel ou clique em outro gráfico); vazio = respeita todos',\r\n section: 'Comportamento'\r\n}\r\n/** Vincula a medida do gráfico a um parâmetro de medida do layout (rosca/KPI). */\r\nconst measureParameterField: FieldSpec = {\r\n kind: 'parameter',\r\n label: 'Medida pelo parâmetro',\r\n hint: 'Segue o slicer de medida escolhido; vazio = usa a medida fixa acima',\r\n section: 'Dados'\r\n}\r\n/** Título da rosca/KPI, com o token `{{medida}}` (vira a descrição da medida atual). */\r\nconst measureTitleField: FieldSpec = {\r\n kind: 'text',\r\n label: 'Título',\r\n hint: 'Use {{medida}} para a descrição da medida atual (muda com o parâmetro de medida)',\r\n section: 'Gráfico'\r\n}\r\n\r\n/**\r\n * Rótulo de cada dimensão/medida do gráfico. Um tipo do app ganha o mesmo editor com um campo\r\n * `{ kind: 'labels' }` (o componente recebe `labels`; ver `fieldLabel`) e `{ kind: 'formats' }`.\r\n */\r\nconst LABELS_FIELD: FieldSpec = {\r\n kind: 'labels',\r\n label: 'Rótulos',\r\n hint: 'Só neste gráfico; vazio = rótulo do dicionário do modelo',\r\n section: 'Rótulos e formatos'\r\n}\r\n/** Formato de cada medida do gráfico (o componente recebe `formats`; ver `measureFormatter`). */\r\nconst FORMATS_FIELD: FieldSpec = {\r\n kind: 'formats',\r\n label: 'Formatos',\r\n hint: 'Só neste gráfico; vazio = formato do dicionário do modelo',\r\n section: 'Rótulos e formatos'\r\n}\r\nconst percentDecimals: FieldSpec = {\r\n kind: 'integer',\r\n min: 0,\r\n max: 6,\r\n default: 1,\r\n label: 'Casas do %',\r\n section: 'Dados'\r\n}\r\n\r\nexport const CHART_FIELDS: Record<ChartType, Record<string, FieldSpec>> = {\r\n kpi: {\r\n title: measureTitleField,\r\n // KPI tem a altura do conteúdo: só título e largura\r\n span: common(0).span as FieldSpec,\r\n measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' },\r\n secondary: {\r\n kind: 'measure',\r\n label: 'Medida secundária',\r\n hint: 'Linha menor embaixo do número (ex.: volume, custo/t)',\r\n section: 'Dados'\r\n },\r\n measureParameter: measureParameterField,\r\n labels: {\r\n ...LABELS_FIELD,\r\n hint: 'O da medida principal aparece acima do número; o da secundária, na linha de baixo'\r\n },\r\n formats: FORMATS_FIELD,\r\n ignoreFilters: ignoreFiltersField\r\n },\r\n combo: {\r\n ...common(360),\r\n maxLabels: {\r\n kind: 'integer',\r\n min: 0,\r\n max: 1000,\r\n default: 24,\r\n label: 'Máx. rótulos',\r\n hint: 'Acima dessa quantidade de barras, os valores aparecem só no tooltip',\r\n section: 'Gráfico'\r\n },\r\n dimension: { kind: 'dimension', required: true, label: 'Dimensão (eixo X)', section: 'Dados' },\r\n formatDimension,\r\n bar: { kind: 'measure', required: true, label: 'Barra', section: 'Dados' },\r\n line: { kind: 'measure', required: true, label: 'Linha', section: 'Dados' },\r\n detail: { kind: 'measure', label: 'Detalhe (2ª linha do rótulo)', section: 'Dados' },\r\n clickToFilter,\r\n hideEmpty,\r\n barColor: { kind: 'color', default: '#F58220', label: 'Cor da barra', section: 'Aparência' },\r\n lineColor: { kind: 'color', default: '#555555', label: 'Cor da linha', section: 'Aparência' },\r\n labels: LABELS_FIELD,\r\n formats: FORMATS_FIELD,\r\n ignoreFilters: ignoreFiltersField\r\n },\r\n bar: {\r\n ...common(360),\r\n dimension: {\r\n kind: 'dimension',\r\n required: true,\r\n label: 'Dimensão (categorias)',\r\n section: 'Dados'\r\n },\r\n formatDimension: { ...formatDimension, of: 'dimension' },\r\n measures: {\r\n kind: 'measures',\r\n required: true,\r\n label: 'Medidas (uma série cada)',\r\n hint: 'Com a série (legenda), só a primeira medida é usada',\r\n section: 'Dados'\r\n },\r\n series: {\r\n kind: 'dimension',\r\n label: 'Série (legenda)',\r\n hint: 'Quebra a medida pelos valores desta dimensão: uma série por valor, com cor fixa por valor (ex.: tipo de descarga)',\r\n section: 'Dados'\r\n },\r\n formatSeries: { ...formatDimension, label: 'Formatar série', of: 'series' },\r\n maxSeries: {\r\n kind: 'integer',\r\n min: 2,\r\n max: 8,\r\n default: 6,\r\n label: 'Máx. séries',\r\n hint: 'Com a série: as menores além dessa quantidade viram \"Outros\"',\r\n section: 'Dados'\r\n },\r\n sort: {\r\n kind: 'enum',\r\n values: ['dimension', 'desc', 'asc'],\r\n labels: { dimension: 'pela dimensão', desc: 'maior primeiro', asc: 'menor primeiro' },\r\n default: 'dimension',\r\n label: 'Ordem',\r\n hint: 'Maior/menor primeiro usa a primeira medida (com a série, o total da barra)',\r\n section: 'Dados'\r\n },\r\n limit: {\r\n kind: 'integer',\r\n min: 1,\r\n max: 1000,\r\n label: 'Só os N primeiros',\r\n hint: 'Aplicado depois de ordenar (ex.: top 10 navios)',\r\n section: 'Dados'\r\n },\r\n clickToFilter: { ...clickToFilter, section: 'Comportamento' },\r\n hideUnselected: {\r\n kind: 'boolean',\r\n default: false,\r\n label: 'Ocultar o resto ao filtrar',\r\n hint: 'Com um filtro na dimensão (clique ou filtro da página), mostra só o selecionado; desligado, o resto fica apagado (transparente)',\r\n section: 'Comportamento'\r\n },\r\n hideEmpty,\r\n showEmpty: {\r\n kind: 'boolean',\r\n default: false,\r\n label: 'Mostrar categorias sem dados',\r\n hint: 'Todos os valores da dimensão no eixo, zerados quando não há dado (ex.: todos os meses). Sobrepõe \"Esconder grupos vazios\"',\r\n section: 'Dados'\r\n },\r\n orientation: {\r\n kind: 'enum',\r\n values: ['vertical', 'horizontal'],\r\n default: 'vertical',\r\n label: 'Orientação',\r\n hint: 'Horizontal cabe nomes longos (navios, funções)',\r\n section: 'Aparência'\r\n },\r\n stacked: {\r\n kind: 'boolean',\r\n default: false,\r\n label: 'Empilhar séries',\r\n hint: 'Só faz sentido com medidas somáveis (partes de um total)',\r\n section: 'Aparência'\r\n },\r\n colors: {\r\n kind: 'colors',\r\n values: 'series',\r\n label: 'Cores',\r\n hint: 'Uma cor por medida ou, com a série, por valor dela; vazio = paleta padrão',\r\n section: 'Aparência'\r\n },\r\n showValues: { kind: 'boolean', default: true, label: 'Mostrar valores', section: 'Valores' },\r\n valuePosition: {\r\n kind: 'enum',\r\n values: BAR_VALUE_POSITIONS,\r\n labels: {\r\n outside: 'fora, na ponta',\r\n insideEnd: 'dentro, na ponta',\r\n center: 'dentro, no meio',\r\n insideBase: 'dentro, na base'\r\n },\r\n default: 'outside',\r\n label: 'Posição do valor',\r\n hint: 'Empilhado: fora não cabe em cada pedaço — use dentro, ou o total na ponta',\r\n section: 'Valores'\r\n },\r\n valueColor: {\r\n kind: 'color',\r\n default: '#333333',\r\n label: 'Cor do valor',\r\n hint: 'Sem cor escolhida: escuro fora da barra, branco dentro',\r\n section: 'Valores'\r\n },\r\n valueSize: {\r\n kind: 'integer',\r\n min: 8,\r\n max: 32,\r\n default: 12,\r\n label: 'Tamanho do valor (px)',\r\n section: 'Valores'\r\n },\r\n showTotal: {\r\n kind: 'boolean',\r\n default: false,\r\n label: 'Total na ponta (empilhado)',\r\n hint: 'Soma da pilha na ponta da barra; só com \"Empilhar séries\"',\r\n section: 'Valores'\r\n },\r\n valueRule: {\r\n kind: 'valueRule',\r\n label: 'Só mostrar valores que atendem a uma regra',\r\n hint: 'Ex.: diferente de 0. Vale para o valor de cada barra (ou pedaço) e para o total; os demais ficam só no tooltip. Percentual como fração (10% = 0.1)',\r\n section: 'Valores'\r\n },\r\n labels: LABELS_FIELD,\r\n formats: FORMATS_FIELD,\r\n ignoreFilters: ignoreFiltersField\r\n },\r\n donut: {\r\n ...common(300),\r\n title: measureTitleField,\r\n dimension: { kind: 'dimension', required: true, label: 'Dimensão (fatias)', section: 'Dados' },\r\n formatDimension,\r\n measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' },\r\n measureParameter: measureParameterField,\r\n maxSlices: {\r\n kind: 'integer',\r\n min: 2,\r\n max: 8,\r\n default: 6,\r\n label: 'Máx. fatias',\r\n hint: 'As menores além dessa quantidade viram \"Outros\"',\r\n section: 'Dados'\r\n },\r\n clickToFilter,\r\n legendPosition: {\r\n kind: 'enum',\r\n values: DONUT_LEGEND_POSITIONS,\r\n labels: { right: 'à direita', left: 'à esquerda', top: 'em cima', bottom: 'embaixo' },\r\n default: 'right',\r\n label: 'Legenda',\r\n hint: 'Dos lados: nome, % e valor; em cima/embaixo: só o nome, em linha',\r\n section: 'Aparência'\r\n },\r\n showTotal: {\r\n kind: 'boolean',\r\n default: true,\r\n label: 'Total no centro',\r\n section: 'Aparência'\r\n },\r\n showValues: {\r\n kind: 'boolean',\r\n default: false,\r\n label: 'Mostrar valores',\r\n hint: 'Valor de cada fatia do lado de fora da rosca (fatias muito pequenas ficam só no tooltip)',\r\n section: 'Valores'\r\n },\r\n valueContent: {\r\n kind: 'enum',\r\n values: DONUT_VALUE_CONTENTS,\r\n labels: { both: 'valor (%)', value: 'só o valor', percent: 'só o %' },\r\n default: 'both',\r\n label: 'Conteúdo do valor',\r\n section: 'Valores'\r\n },\r\n labels: LABELS_FIELD,\r\n formats: FORMATS_FIELD,\r\n percentDecimals,\r\n ignoreFilters: ignoreFiltersField\r\n },\r\n table: {\r\n ...common(400),\r\n dimensions: { kind: 'dimensions', label: 'Dimensões (linhas)', section: 'Dados' },\r\n formatDimensions: {\r\n kind: 'formatters',\r\n label: 'Formatar dimensões',\r\n hint: 'Formatters passados ao <DashboardCharts formatters={{ ... }}> da página',\r\n section: 'Dados'\r\n },\r\n measures: { kind: 'measures', required: true, label: 'Medidas (colunas)', section: 'Dados' },\r\n columnOrder: {\r\n kind: 'columnOrder',\r\n label: 'Ordem das colunas',\r\n hint: 'Dimensões, medidas e % do total em qualquer ordem; vazio = dimensões e depois medidas (cada % logo depois da medida)',\r\n section: 'Dados'\r\n },\r\n percentOf: {\r\n kind: 'measures',\r\n percent: true,\r\n label: '% do total',\r\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.',\r\n section: 'Dados'\r\n },\r\n sortBy: {\r\n kind: 'column',\r\n outside: true,\r\n label: 'Ordenar por',\r\n hint: 'Ordem inicial; quem usa reordena clicando no cabeçalho. Pode ser uma dimensão/medida fora da tabela (ex.: número do mês para o nome do mês) — uma dimensão de fora divide as linhas se tiver mais de um valor por linha.',\r\n section: 'Dados'\r\n },\r\n sortDirection: {\r\n kind: 'enum',\r\n values: ['desc', 'asc'],\r\n labels: { desc: 'decrescente', asc: 'crescente' },\r\n default: 'desc',\r\n label: 'Direção',\r\n section: 'Dados'\r\n },\r\n showTotal: { kind: 'boolean', default: true, label: 'Linha de total', section: 'Dados' },\r\n clickToFilter: {\r\n ...clickToFilter,\r\n hint: 'Clicar numa linha filtra pela primeira dimensão'\r\n },\r\n cellBorders: {\r\n kind: 'boolean',\r\n default: false,\r\n label: 'Bordas entre as células',\r\n hint: 'Grade em volta de cada célula; desligado = só a linha embaixo de cada linha',\r\n section: 'Aparência'\r\n },\r\n borderColor: {\r\n kind: 'color',\r\n default: '#e0e0e0',\r\n label: 'Cor das bordas',\r\n section: 'Aparência'\r\n },\r\n cellPadding: {\r\n kind: 'integer',\r\n min: 0,\r\n max: 40,\r\n default: 8,\r\n label: 'Espaçamento das células (px)',\r\n hint: 'Espaço em cima e embaixo de cada célula',\r\n section: 'Aparência'\r\n },\r\n fontSize: {\r\n kind: 'integer',\r\n min: 8,\r\n max: 32,\r\n default: 13,\r\n label: 'Tamanho da fonte (px)',\r\n hint: 'Fonte das células e do cabeçalho',\r\n section: 'Aparência'\r\n },\r\n labels: LABELS_FIELD,\r\n formats: FORMATS_FIELD,\r\n align: {\r\n kind: 'aligns',\r\n label: 'Alinhamento',\r\n hint: 'Alinhamento horizontal de cada coluna; vazio = dimensão à esquerda, número à direita',\r\n section: 'Rótulos e formatos'\r\n },\r\n verticalAlign: {\r\n kind: 'verticalAligns',\r\n label: 'Alinhamento vertical',\r\n hint: 'Alinhamento vertical de cada coluna (aparece com texto em mais de uma linha); vazio = no meio',\r\n section: 'Rótulos e formatos'\r\n },\r\n percentDecimals,\r\n formatRules: {\r\n kind: 'formatRules',\r\n label: 'Regras',\r\n hint: 'Muda a cor do texto, o fundo ou o negrito da célula ou da linha conforme o valor de uma dimensão/medida (pode ser de fora da tabela). Regras de linha primeiro, de célula por cima; a de baixo ganha.',\r\n section: 'Formatação condicional'\r\n },\r\n colorScales: {\r\n kind: 'colorScales',\r\n label: 'Escalas de cor',\r\n hint: 'Gradiente numa coluna numérica: a cor vai da mínima à máxima conforme o valor (ex.: 0 a 100). Mínimo/máximo vazios = menor/maior valor da coluna na tabela. As regras ficam por cima.',\r\n section: 'Formatação condicional'\r\n },\r\n ignoreFilters: ignoreFiltersField\r\n }\r\n}\r\n\r\n// --- registro de tipos ---\r\n\r\n/**\r\n * Tipo de gráfico: nome no devtools e campos do JSON. Para um tipo do app, `title`, `span` e\r\n * `height` entram sozinhos (`height: null` = sem altura, como o KPI) — `fields` tem só os do\r\n * componente. Objeto puro (sem React): o mesmo arquivo serve o client e o servidor.\r\n *\r\n * ```ts\r\n * export const rankingType = {\r\n * label: 'Ranking',\r\n * height: 300,\r\n * fields: {\r\n * dimension: { kind: 'dimension', required: true, label: 'Dimensão', section: 'Dados' },\r\n * measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' }\r\n * }\r\n * } satisfies ChartTypeSpec\r\n * ```\r\n */\r\nexport type ChartTypeSpec = {\r\n label: string\r\n fields: Record<string, FieldSpec>\r\n /** Altura padrão em px (padrão: 300); `null` = o componente tem a altura do conteúdo. */\r\n height?: number | null\r\n}\r\n\r\n/** Registro completo (lib + app), com os campos comuns já incluídos. Ver `resolveChartTypes`. */\r\nexport type ChartTypes = Record<string, { label: string; fields: Record<string, FieldSpec> }>\r\n\r\nexport const BUILTIN_CHART_TYPES: ChartTypes = Object.fromEntries(\r\n CHART_TYPES.map((type) => [type, { label: CHART_TYPE_LABEL[type], fields: CHART_FIELDS[type] }])\r\n)\r\n\r\n/**\r\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\r\n * (aparece na subida, não no meio da edição).\r\n */\r\nexport function resolveChartTypes(custom: Record<string, ChartTypeSpec> = {}): ChartTypes {\r\n const types: ChartTypes = { ...BUILTIN_CHART_TYPES }\r\n for (const [name, spec] of Object.entries(custom)) {\r\n if (BUILTIN_CHART_TYPES[name])\r\n throw new Error(`Tipo de gráfico \"${name}\" já existe na lib: escolha outro nome`)\r\n if (!LAYOUT_ID.test(name))\r\n throw new Error(`Tipo de gráfico \"${name}\": use letras, números, \"-\" e \"_\"`)\r\n const { title, span, height } = common(spec.height ?? 300)\r\n const base =\r\n spec.height === null\r\n ? { title, span, ignoreFilters: ignoreFiltersField }\r\n : { title, span, height, ignoreFilters: ignoreFiltersField }\r\n types[name] = {\r\n label: spec.label,\r\n fields: { ...base, ...spec.fields } as Record<string, FieldSpec>\r\n }\r\n }\r\n return types\r\n}\r\n\r\n/**\r\n * Props que o componente de um tipo do app recebe: os campos do gráfico no JSON (com os `default`\r\n * dos campos aplicados e os formatters já trocados pelas funções), sem `id`, `type`, `title` e\r\n * `span`, mais o `queryLabel` (nome da consulta no devtools).\r\n */\r\nexport function customChartProps(\r\n chart: ChartDef,\r\n types: ChartTypes,\r\n formatters: Record<string, unknown>\r\n): Record<string, unknown> {\r\n const values = chart as Record<string, unknown>\r\n const props: Record<string, unknown> = {}\r\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\r\n if (key === 'title' || key === 'span') continue\r\n const value = values[key] ?? spec.default\r\n if (value === undefined) continue\r\n if (spec.kind === 'formatter') props[key] = formatters[value as string]\r\n else if (spec.kind === 'formatters')\r\n props[key] = Object.fromEntries(\r\n Object.entries(value as Record<string, string>)\r\n .filter(([, name]) => formatters[name])\r\n .map(([dimension, name]) => [dimension, formatters[name]])\r\n )\r\n else props[key] = value\r\n }\r\n return { ...props, queryLabel: `${chart.title || chart.id} · ${chart.type}` }\r\n}\r\n\r\nexport const FILTER_FIELDS: Record<string, FieldSpec> = {\r\n dimension: { kind: 'dimension', required: true, label: 'Dimensão', section: 'Filtro' },\r\n label: {\r\n kind: 'text',\r\n label: 'Rótulo',\r\n hint: 'Padrão: rótulo da dimensão no dicionário',\r\n section: 'Filtro'\r\n },\r\n width: {\r\n kind: 'integer',\r\n min: 80,\r\n max: 800,\r\n default: 200,\r\n label: 'Largura (px)',\r\n section: 'Filtro'\r\n },\r\n formatLabel: {\r\n kind: 'formatter',\r\n label: 'Formatar opções',\r\n hint: 'Formatters passados ao <DashboardFilters formatters={{ ... }}> da página',\r\n section: 'Filtro'\r\n },\r\n ignoredBy: {\r\n kind: 'charts',\r\n label: 'Gráficos que ignoram',\r\n hint: 'Gráficos que não reagem a este filtro; vazio = todos reagem',\r\n section: 'Filtro'\r\n }\r\n}\r\n\r\n/** Campos do formulário de um parâmetro de medida no devtools (ver `ParameterDef`). */\r\nexport const PARAMETER_FIELDS: Record<string, FieldSpec> = {\r\n name: {\r\n kind: 'text',\r\n required: true,\r\n label: 'Id',\r\n hint: 'Identificador do parâmetro (letras, números, \"-\" e \"_\")',\r\n section: 'Filtro'\r\n },\r\n label: { kind: 'text', label: 'Rótulo', hint: 'Padrão: \"Medida\"', section: 'Filtro' },\r\n measures: {\r\n kind: 'measures',\r\n required: true,\r\n label: 'Medidas (opções)',\r\n hint: 'Medidas que o slicer oferece',\r\n section: 'Filtro'\r\n },\r\n default: {\r\n kind: 'column',\r\n label: 'Medida inicial',\r\n hint: 'Padrão: a primeira das opções',\r\n section: 'Filtro'\r\n },\r\n width: { kind: 'integer', min: 80, max: 800, default: 200, label: 'Largura (px)', section: 'Filtro' }\r\n}\r\n\r\nexport const CANVAS_FIELDS: Record<string, FieldSpec> = {\r\n width: {\r\n kind: 'integer',\r\n min: 320,\r\n max: 7680,\r\n required: true,\r\n label: 'Largura (px)',\r\n section: 'Tela'\r\n },\r\n height: {\r\n kind: 'integer',\r\n min: 200,\r\n max: 4320,\r\n label: 'Altura mínima (px)',\r\n hint: 'Conteúdo mais alto que isso estende a tela',\r\n section: 'Tela'\r\n }\r\n}\r\n\r\n/** Campos de `CardStyle` (o `card` de um gráfico). */\r\nexport const CARD_FIELDS: Record<string, FieldSpec> = {\r\n background: { kind: 'color', default: '#ffffff', label: 'Fundo', section: 'Card' },\r\n borderColor: { kind: 'color', default: '#e0e0e0', label: 'Cor da borda', section: 'Card' },\r\n borderWidth: {\r\n kind: 'integer',\r\n min: 0,\r\n max: 8,\r\n default: 1,\r\n label: 'Borda (px)',\r\n hint: '0 = sem borda',\r\n section: 'Card'\r\n },\r\n borderRadius: {\r\n kind: 'integer',\r\n min: 0,\r\n max: 40,\r\n default: 4,\r\n label: 'Cantos (px)',\r\n section: 'Card'\r\n },\r\n shadow: {\r\n kind: 'integer',\r\n min: 0,\r\n max: 24,\r\n default: 0,\r\n label: 'Sombra',\r\n hint: 'Elevação do MUI: 0 = sem sombra, 1 a 4 = sutil, acima disso = forte',\r\n section: 'Card'\r\n },\r\n padding: {\r\n kind: 'integer',\r\n min: 0,\r\n max: 64,\r\n default: 12,\r\n label: 'Espaço interno (px)',\r\n section: 'Card'\r\n },\r\n titleColor: { kind: 'color', default: '#212121', label: 'Cor do título', section: 'Card' },\r\n titleSize: {\r\n kind: 'integer',\r\n min: 10,\r\n max: 40,\r\n default: 16,\r\n label: 'Tamanho do título (px)',\r\n section: 'Card'\r\n },\r\n titleAlign: {\r\n kind: 'enum',\r\n values: ['left', 'center', 'right'],\r\n labels: { left: 'à esquerda', center: 'centralizado', right: 'à direita' },\r\n default: 'left',\r\n label: 'Alinhamento do título',\r\n section: 'Card'\r\n },\r\n titleDivider: {\r\n kind: 'boolean',\r\n default: false,\r\n label: 'Divisor do título',\r\n hint: 'Linha abaixo do título separando do conteúdo',\r\n section: 'Card'\r\n },\r\n titleDividerColor: {\r\n kind: 'color',\r\n default: '#e0e0e0',\r\n label: 'Cor do divisor',\r\n section: 'Card'\r\n },\r\n titleGap: {\r\n kind: 'integer',\r\n min: 0,\r\n max: 40,\r\n default: 4,\r\n label: 'Espaço título → divisor (px)',\r\n hint: 'Só com o divisor ligado',\r\n section: 'Card'\r\n },\r\n contentGap: {\r\n kind: 'integer',\r\n min: 0,\r\n max: 40,\r\n default: 8,\r\n label: 'Espaço título → conteúdo (px)',\r\n hint: 'Abaixo do título (ou do divisor), antes do gráfico',\r\n section: 'Card'\r\n }\r\n}\r\n\r\nconst gapField = (label: string): FieldSpec => ({\r\n kind: 'integer',\r\n min: 0,\r\n max: 64,\r\n default: 12,\r\n label,\r\n section: 'Card'\r\n})\r\n\r\n/** Campos de `CardsStyle` (`layout.cards`): o espaço entre os cards e o estilo padrão deles. */\r\nexport const CARDS_FIELDS: Record<string, FieldSpec> = {\r\n rowGap: gapField('Espaço entre linhas (px)'),\r\n columnGap: gapField('Espaço entre colunas (px)'),\r\n ...CARD_FIELDS\r\n}\r\n\r\n/** Espaço entre os cards em px (`undefined` = o `gap` do componente). */\r\nexport function cardsGap(cards: CardsStyle | undefined) {\r\n return { row: cards?.rowGap ?? cards?.gap, column: cards?.columnGap ?? cards?.gap }\r\n}\r\n\r\n/** Estilo de um card: o padrão do layout com o do gráfico por cima. */\r\nexport function chartCardStyle(layout: ChartsLayout, chart: ChartDef): CardStyle {\r\n const { gap: _gap, rowGap: _row, columnGap: _column, ...defaults } = layout.cards ?? {}\r\n return { ...defaults, ...chart.card }\r\n}\r\n\r\n/** Tamanhos comuns de tela para o devtools. */\r\nexport const CANVAS_PRESETS: CanvasDef[] = [\r\n { width: 1920, height: 1080 },\r\n { width: 1600, height: 900 },\r\n { width: 1366, height: 768 },\r\n { width: 1280, height: 720 }\r\n]\r\n\r\n// --- validação ---\r\n\r\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\r\n typeof value === 'object' && value !== null && !Array.isArray(value)\r\n\r\nconst isName = (value: unknown): value is string => typeof value === 'string' && !!value\r\n\r\nfunction fieldError(spec: FieldSpec, value: unknown): string | null {\r\n if (value === undefined) return spec.required ? 'é obrigatório' : null\r\n switch (spec.kind) {\r\n case 'text':\r\n return typeof value === 'string' ? null : 'precisa ser texto'\r\n case 'color':\r\n return typeof value === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(value)\r\n ? null\r\n : 'precisa ser uma cor (#rrggbb)'\r\n case 'boolean':\r\n return typeof value === 'boolean' ? null : 'precisa ser true/false'\r\n case 'integer':\r\n return typeof value === 'number' &&\r\n Number.isInteger(value) &&\r\n value >= spec.min &&\r\n value <= spec.max\r\n ? null\r\n : `precisa ser um inteiro de ${spec.min} a ${spec.max}`\r\n case 'enum':\r\n return spec.values.includes(value as string) ? null : `precisa ser ${spec.values.join(', ')}`\r\n case 'dimension':\r\n case 'measure':\r\n case 'formatter':\r\n case 'column':\r\n case 'parameter':\r\n return isName(value) ? null : spec.required ? 'é obrigatório' : 'precisa ser texto'\r\n case 'dimensions':\r\n case 'measures':\r\n case 'columnOrder':\r\n case 'filters':\r\n case 'charts':\r\n if (!Array.isArray(value) || !value.every(isName)) return 'precisa ser uma lista de nomes'\r\n if (spec.required && !value.length) return 'precisa de pelo menos um item'\r\n return new Set(value).size === value.length ? null : 'tem item repetido'\r\n case 'formatters':\r\n return isRecord(value) && Object.values(value).every(isName)\r\n ? null\r\n : 'precisa ser { dimensão: formatter }'\r\n case 'labels':\r\n return isRecord(value) && Object.values(value).every((label) => typeof label === 'string')\r\n ? null\r\n : 'precisa ser { coluna: rótulo }'\r\n case 'aligns':\r\n return isRecord(value) &&\r\n Object.values(value).every((align) => COLUMN_ALIGNS.includes(align as ColumnAlign))\r\n ? null\r\n : `precisa ser { coluna: ${COLUMN_ALIGNS.join(' | ')} }`\r\n case 'verticalAligns':\r\n return isRecord(value) &&\r\n Object.values(value).every((align) =>\r\n COLUMN_VERTICAL_ALIGNS.includes(align as ColumnVerticalAlign)\r\n )\r\n ? null\r\n : `precisa ser { coluna: ${COLUMN_VERTICAL_ALIGNS.join(' | ')} }`\r\n case 'formatRules': {\r\n if (!Array.isArray(value)) return 'precisa ser uma lista de regras'\r\n for (const [index, rule] of value.entries()) {\r\n const error = formatRuleError(rule)\r\n if (error) return `regra ${index + 1}: ${error}`\r\n }\r\n return null\r\n }\r\n case 'colorScales': {\r\n if (!Array.isArray(value)) return 'precisa ser uma lista de escalas'\r\n for (const [index, scale] of value.entries()) {\r\n const error = colorScaleError(scale)\r\n if (error) return `escala ${index + 1}: ${error}`\r\n }\r\n return null\r\n }\r\n case 'valueRule': {\r\n const error = valueRuleError(value)\r\n return error && isRecord(value) ? `inválido: ${error}` : error\r\n }\r\n case 'colors':\r\n return isRecord(value) &&\r\n Object.values(value).every(\r\n (color) => typeof color === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(color)\r\n )\r\n ? null\r\n : 'precisa ser { série: cor (#rrggbb) }'\r\n case 'formats': {\r\n if (!isRecord(value)) return 'precisa ser { medida: formato }'\r\n for (const [measure, format] of Object.entries(value)) {\r\n const error = numberFormatError(format)\r\n if (error) return `\"${measure}\": ${error}`\r\n }\r\n return null\r\n }\r\n }\r\n}\r\n\r\nconst NUMBER_FORMAT_KEYS = ['format', 'decimals', 'compact', 'prefix', 'suffix']\r\n\r\nfunction numberFormatError(format: unknown): string | null {\r\n if (!isRecord(format)) return 'precisa ser um objeto'\r\n const unknown = Object.keys(format).find((key) => !NUMBER_FORMAT_KEYS.includes(key))\r\n if (unknown) return `campo desconhecido \\`${unknown}\\``\r\n const { decimals } = format\r\n if (format.format !== undefined && !VALUE_FORMATS.includes(format.format as ValueFormat))\r\n return `formato inválido \"${String(format.format)}\"`\r\n if (\r\n decimals !== undefined &&\r\n (typeof decimals !== 'number' || !Number.isInteger(decimals) || decimals < 0 || decimals > 10)\r\n )\r\n return '`decimals` precisa ser um inteiro de 0 a 10'\r\n if (format.compact !== undefined && typeof format.compact !== 'boolean')\r\n return '`compact` precisa ser true/false'\r\n for (const key of ['prefix', 'suffix'])\r\n if (format[key] !== undefined && typeof format[key] !== 'string')\r\n return `\\`${key}\\` precisa ser texto`\r\n return null\r\n}\r\n\r\n/**\r\n * Problemas de forma do layout (vazio = válido). Confere a forma do objeto porque o rascunho do\r\n * devtools chega como JSON da request.\r\n */\r\nexport function validateLayout(\r\n layout: ChartsLayout,\r\n types: ChartTypes = BUILTIN_CHART_TYPES\r\n): string[] {\r\n if (!isRecord(layout)) return ['layout inválido']\r\n if (typeof layout.id !== 'string' || !LAYOUT_ID.test(layout.id))\r\n return ['`id` do layout: letras, números, \"-\" e \"_\" (até 80)']\r\n if (!Array.isArray(layout.charts)) return ['`charts` precisa ser uma lista']\r\n\r\n const errors: string[] = []\r\n const ids = new Set<string>()\r\n layout.charts.forEach((chart: unknown, index) => {\r\n const context = `gráfico ${index + 1}`\r\n if (!isRecord(chart)) {\r\n errors.push(`${context} inválido`)\r\n return\r\n }\r\n if (typeof chart.id !== 'string' || !LAYOUT_ID.test(chart.id))\r\n errors.push(`${context}: \\`id\\` inválido`)\r\n else if (ids.has(chart.id)) errors.push(`${context}: id repetido \"${chart.id}\"`)\r\n else ids.add(chart.id)\r\n\r\n const fields =\r\n typeof chart.type === 'string' && Object.hasOwn(types, chart.type)\r\n ? types[chart.type]?.fields\r\n : undefined\r\n if (!fields) {\r\n errors.push(`${context}: tipo inválido \"${chart.type}\"`)\r\n return\r\n }\r\n if (typeof chart.title !== 'string') errors.push(`${context}: \\`title\\` precisa ser texto`)\r\n const rest = Object.fromEntries(Object.entries(fields).filter(([key]) => key !== 'title'))\r\n errors.push(...fieldsErrors(context, rest, chart, ['id', 'type', 'title', 'card']))\r\n if (chart.card !== undefined) {\r\n if (!isRecord(chart.card)) errors.push(`${context}: \\`card\\` precisa ser um objeto`)\r\n else errors.push(...fieldsErrors(`${context}: card`, CARD_FIELDS, chart.card))\r\n }\r\n })\r\n\r\n if (layout.cards !== undefined) {\r\n if (!isRecord(layout.cards)) errors.push('`cards` precisa ser um objeto')\r\n else\r\n errors.push(...fieldsErrors('cards', { ...CARDS_FIELDS, gap: gapField('gap') }, layout.cards))\r\n }\r\n\r\n if (layout.canvas !== undefined) {\r\n if (!isRecord(layout.canvas)) errors.push('`canvas` precisa ser { width, height? }')\r\n else errors.push(...fieldsErrors('canvas', CANVAS_FIELDS, layout.canvas))\r\n }\r\n\r\n if (layout.filters !== undefined) {\r\n if (!Array.isArray(layout.filters)) return [...errors, '`filters` precisa ser uma lista']\r\n const dimensions = new Set<string>()\r\n layout.filters.forEach((filter: unknown, index) => {\r\n const context = `filtro ${index + 1}`\r\n if (!isRecord(filter)) {\r\n errors.push(`${context} inválido`)\r\n return\r\n }\r\n errors.push(...fieldsErrors(context, FILTER_FIELDS, filter))\r\n if (isName(filter.dimension)) {\r\n if (dimensions.has(filter.dimension))\r\n errors.push(`${context}: dimensão repetida \"${filter.dimension}\"`)\r\n dimensions.add(filter.dimension)\r\n }\r\n if (Array.isArray(filter.ignoredBy))\r\n for (const id of filter.ignoredBy)\r\n if (isName(id) && !ids.has(id))\r\n errors.push(`${context}: gráfico \"${id}\" de \\`ignoredBy\\` não existe`)\r\n })\r\n }\r\n\r\n if (layout.parameters !== undefined) {\r\n if (!Array.isArray(layout.parameters)) return [...errors, '`parameters` precisa ser uma lista']\r\n const names = new Set<string>()\r\n layout.parameters.forEach((parameter: unknown, index) => {\r\n const context = `parâmetro ${index + 1}`\r\n if (!isRecord(parameter)) {\r\n errors.push(`${context} inválido`)\r\n return\r\n }\r\n if (typeof parameter.name !== 'string' || !LAYOUT_ID.test(parameter.name))\r\n errors.push(`${context}: \\`name\\` inválido`)\r\n else if (names.has(parameter.name))\r\n errors.push(`${context}: name repetido \"${parameter.name}\"`)\r\n else names.add(parameter.name)\r\n if (parameter.label !== undefined && typeof parameter.label !== 'string')\r\n errors.push(`${context}: \\`label\\` precisa ser texto`)\r\n if (!Array.isArray(parameter.measures) || !parameter.measures.every(isName))\r\n errors.push(`${context}: \\`measures\\` precisa ser uma lista de medidas`)\r\n else if (!parameter.measures.length)\r\n errors.push(`${context}: \\`measures\\` precisa de pelo menos uma medida`)\r\n else if (new Set(parameter.measures).size !== parameter.measures.length)\r\n errors.push(`${context}: \\`measures\\` tem medida repetida`)\r\n if (\r\n parameter.default !== undefined &&\r\n (!isName(parameter.default) ||\r\n (Array.isArray(parameter.measures) && !parameter.measures.includes(parameter.default)))\r\n )\r\n errors.push(`${context}: \\`default\\` precisa ser uma das medidas`)\r\n if (parameter.width !== undefined && typeof parameter.width !== 'number')\r\n errors.push(`${context}: \\`width\\` precisa ser um número`)\r\n })\r\n }\r\n return errors\r\n}\r\n\r\nfunction fieldsErrors(\r\n context: string,\r\n fields: Record<string, FieldSpec>,\r\n values: Record<string, unknown>,\r\n extraKeys: string[] = []\r\n) {\r\n const errors: string[] = []\r\n for (const [key, spec] of Object.entries(fields)) {\r\n const error = fieldError(spec, values[key])\r\n if (error) errors.push(`${context}: \\`${key}\\` ${error}`)\r\n }\r\n const known = new Set([...extraKeys, ...Object.keys(fields)])\r\n for (const key of Object.keys(values))\r\n if (!known.has(key)) errors.push(`${context}: campo desconhecido \\`${key}\\``)\r\n return errors\r\n}\r\n\r\n/** Dimensões, medidas e formatters que o gráfico referencia (lidos pelos campos do tipo). */\r\nexport const chartReferences = (chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) =>\r\n fieldReferences(types[chart.type]?.fields ?? {}, chart)\r\n\r\n/** Idem para um filtro da página. */\r\nexport const filterReferences = (filter: FilterDef) => fieldReferences(FILTER_FIELDS, filter)\r\n\r\nfunction fieldReferences(fields: Record<string, FieldSpec>, item: object) {\r\n const refs = { dimensions: [] as string[], measures: [] as string[], formatters: [] as string[] }\r\n const values = item as Record<string, unknown>\r\n for (const [key, spec] of Object.entries(fields)) {\r\n const value = values[key]\r\n if (value === undefined || value === '') continue\r\n if (spec.kind === 'dimension') refs.dimensions.push(value as string)\r\n if (spec.kind === 'dimensions') refs.dimensions.push(...(value as string[]))\r\n if (spec.kind === 'filters') refs.dimensions.push(...(value as string[]))\r\n if (spec.kind === 'measure') refs.measures.push(value as string)\r\n if (spec.kind === 'measures') refs.measures.push(...(value as string[]))\r\n if (spec.kind === 'formatter') refs.formatters.push(value as string)\r\n if (spec.kind === 'formatters') refs.formatters.push(...Object.values(value as object))\r\n }\r\n return refs\r\n}\r\n\r\n/**\r\n * Colunas que o gráfico mostra (para `sortBy`, rótulos): as dimensões e medidas escolhidas nele e os\r\n * percentuais (`%:medida`), na ordem do campo `columnOrder` (se o tipo tiver) — ver `tableColumns`.\r\n */\r\nexport function chartColumns(chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) {\r\n const values = chart as Record<string, unknown>\r\n const columns: string[] = []\r\n const percents: string[] = []\r\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\r\n const value = values[key]\r\n if (value === undefined || value === '') continue\r\n if (spec.kind === 'dimension' || spec.kind === 'measure') columns.push(value as string)\r\n if (spec.kind === 'dimensions') columns.push(...(value as string[]))\r\n if (spec.kind === 'measures') (spec.percent ? percents : columns).push(...(value as string[]))\r\n }\r\n return tableColumns([...new Set(columns)], [...new Set(percents)], columnOrderOf(chart, types))\r\n}\r\n\r\n/** Prefixo da coluna de % do total de uma medida no `columnOrder` (`%:custoTotal`). */\r\nexport const PERCENT_PREFIX = '%:'\r\nexport const percentColumn = (measure: string) => `${PERCENT_PREFIX}${measure}`\r\n\r\n/**\r\n * Ordem das colunas: as de `order` primeiro, o resto depois, na ordem em que veio (`orderedColumns`).\r\n * O % de uma medida que não está em `order` vem logo depois da medida (ou no fim, se ela não é\r\n * coluna) — layouts sem `%:` no `columnOrder` continuam como antes.\r\n */\r\nexport function tableColumns(columns: string[], percentOf: string[], order?: string[]) {\r\n const percents = percentOf.map(percentColumn)\r\n const result = orderedColumns(\r\n [...columns, ...percents.filter((column) => order?.includes(column))],\r\n order\r\n )\r\n for (const measure of percentOf) {\r\n const column = percentColumn(measure)\r\n if (result.includes(column)) continue\r\n const at = result.indexOf(measure)\r\n if (at === -1) result.push(column)\r\n else result.splice(at + 1, 0, column)\r\n }\r\n return result\r\n}\r\n\r\n/** Valor do campo `columnOrder` do gráfico (o tipo pode não ter). */\r\nexport function columnOrderOf(chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) {\r\n const values = chart as Record<string, unknown>\r\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {}))\r\n if (spec.kind === 'columnOrder' && Array.isArray(values[key])) return values[key] as string[]\r\n return undefined\r\n}\r\n\r\n/** `columns` na ordem de `order`; os que não estão nela vão depois, na ordem em que vieram. */\r\nexport function orderedColumns(columns: string[], order: string[] | undefined) {\r\n if (!order?.length) return columns\r\n const first = order.filter((name) => columns.includes(name))\r\n return [...first, ...columns.filter((name) => !first.includes(name))]\r\n}\r\n\r\n/**\r\n * Dimensões/medidas que o layout usa e o modelo não tem. Não impede salvar (o modelo pode estar\r\n * sendo editado junto), mas o gráfico vai mostrar erro.\r\n */\r\nexport function layoutModelWarnings(\r\n layout: ChartsLayout,\r\n model: Pick<ModelDef, 'dimensions' | 'measures'>,\r\n types: ChartTypes = BUILTIN_CHART_TYPES\r\n): string[] {\r\n const warnings: string[] = []\r\n for (const chart of layout.charts) {\r\n const name = `\"${chart.title || chart.id}\"`\r\n const { dimensions, measures } = chartReferences(chart, types)\r\n for (const dimension of dimensions)\r\n if (!model.dimensions[dimension]) warnings.push(`${name}: dimensão \"${dimension}\" não existe`)\r\n for (const measure of measures)\r\n if (!model.measures[measure]) warnings.push(`${name}: medida \"${measure}\" não existe`)\r\n if (isBuiltinChart(chart) && chart.type === 'bar' && chart.series) {\r\n if (chart.series === chart.dimension)\r\n warnings.push(`${name}: a série é a própria dimensão das categorias`)\r\n if (chart.measures.length > 1)\r\n warnings.push(`${name}: com série, só a primeira medida (\"${chart.measures[0]}\") é usada`)\r\n }\r\n // Campos `column` (ex.: `sortBy`) e as chaves de `labels`/`formats` precisam ser colunas\r\n // escolhidas no gráfico\r\n const columns = chartColumns(chart, types)\r\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\r\n const value = (chart as Record<string, unknown>)[key]\r\n if (spec.kind === 'column' && isName(value) && !columns.includes(value))\r\n warnings.push(\r\n spec.outside\r\n ? isModelField(model, value)\r\n ? ''\r\n : `${name}: \"${value}\" (${key}) não existe no modelo`\r\n : `${name}: \"${value}\" (${key}) não é coluna do gráfico`\r\n )\r\n // Cores por medida (sem a dimensão de valores preenchida): a chave tem que ser medida do gráfico\r\n if (\r\n spec.kind === 'colors' &&\r\n isRecord(value) &&\r\n !(spec.values && (chart as Record<string, unknown>)[spec.values])\r\n )\r\n for (const series of Object.keys(value))\r\n if (!measures.includes(series))\r\n warnings.push(`${name}: cor de \"${series}\" (${key}), que não é medida do gráfico`)\r\n if (spec.kind === 'formatRules' && Array.isArray(value))\r\n value.forEach((rule: unknown, index) => {\r\n if (!isRecord(rule)) return\r\n const context = `${name}: regra ${index + 1} (${key})`\r\n if (\r\n isName(rule.field) &&\r\n !columns.includes(rule.field) &&\r\n !isModelField(model, rule.field)\r\n )\r\n warnings.push(`${context}: \"${rule.field}\" não existe no modelo`)\r\n const painted = rule.target === 'row' ? undefined : (rule.column ?? rule.field)\r\n if (isName(painted) && !columns.includes(painted))\r\n warnings.push(`${context}: \"${painted}\" não é coluna do gráfico (nada é pintado)`)\r\n })\r\n if (spec.kind === 'colorScales' && Array.isArray(value))\r\n value.forEach((scale: unknown, index) => {\r\n if (isRecord(scale) && isName(scale.column) && !columns.includes(scale.column))\r\n warnings.push(\r\n `${name}: escala ${index + 1} (${key}): \"${scale.column}\" não é coluna do gráfico`\r\n )\r\n })\r\n if (\r\n (spec.kind === 'labels' ||\r\n spec.kind === 'formats' ||\r\n spec.kind === 'aligns' ||\r\n spec.kind === 'verticalAligns') &&\r\n isRecord(value)\r\n )\r\n for (const column of Object.keys(value))\r\n if (!columns.includes(column))\r\n warnings.push(`${name}: \"${column}\" (${key}) não é coluna do gráfico`)\r\n if (spec.kind === 'columnOrder' && Array.isArray(value))\r\n for (const column of value)\r\n if (isName(column) && !columns.includes(column))\r\n warnings.push(`${name}: \"${column}\" (${key}) não é coluna do gráfico`)\r\n }\r\n }\r\n for (const filter of layout.filters ?? [])\r\n if (!model.dimensions[filter.dimension])\r\n warnings.push(\r\n `filtro \"${filter.label || filter.dimension}\": dimensão \"${filter.dimension}\" não existe`\r\n )\r\n for (const parameter of layout.parameters ?? [])\r\n for (const measure of parameter.measures ?? [])\r\n if (!model.measures[measure])\r\n warnings.push(\r\n `parâmetro \"${parameter.label || parameter.name}\": medida \"${measure}\" não existe`\r\n )\r\n const parameterNames = new Set((layout.parameters ?? []).map((parameter) => parameter.name))\r\n for (const chart of layout.charts) {\r\n const linked = (chart as { measureParameter?: unknown }).measureParameter\r\n if (typeof linked === 'string' && linked && !parameterNames.has(linked))\r\n warnings.push(`\"${chart.title || chart.id}\": parâmetro de medida \"${linked}\" não existe`)\r\n }\r\n return warnings.filter(Boolean)\r\n}\r\n\r\nconst isModelField = (model: Pick<ModelDef, 'dimensions' | 'measures'>, name: string) =>\r\n Object.hasOwn(model.dimensions, name) || Object.hasOwn(model.measures, name)\r\n\r\n/**\r\n * Gráfico novo de um tipo, reaproveitando o que der do anterior (troca de tipo no devtools) ou\r\n * escolhendo dimensão/medidas do modelo.\r\n */\r\nexport function chartOfType(\r\n type: string,\r\n model: Pick<ModelDef, 'dimensions' | 'measures'>,\r\n base: Pick<BaseChartDef, 'id' | 'title' | 'span'> & Partial<ChartDef>,\r\n types: ChartTypes = BUILTIN_CHART_TYPES\r\n): ChartDef {\r\n const { id, title, span, card, labels, formats } = base as BaseChartDef & ColumnsDisplay\r\n const previous = chartReferences(base as ChartDef, types)\r\n const measures = Object.entries(model.measures)\r\n const firstSum = measures.find(([, measure]) => isAggregate(measure))?.[0]\r\n const firstRatio = measures.find(([, measure]) => measure.type === 'ratio')?.[0]\r\n const dimension = previous.dimensions[0] ?? Object.keys(model.dimensions)[0] ?? ''\r\n const measure = previous.measures[0] ?? firstSum ?? measures[0]?.[0] ?? ''\r\n // Rótulos e formatos seguem se o tipo novo tiver os campos (as chaves que sobrarem viram aviso)\r\n const fields = types[type]?.fields ?? {}\r\n const shared = {\r\n id,\r\n title,\r\n ...(span !== undefined && { span }),\r\n ...(card && { card }),\r\n ...(labels && fields.labels?.kind === 'labels' && { labels }),\r\n ...(formats && fields.formats?.kind === 'formats' && { formats })\r\n }\r\n switch (type) {\r\n case 'kpi':\r\n return {\r\n ...shared,\r\n type,\r\n measure,\r\n ...(previous.measures[1] && { secondary: previous.measures[1] })\r\n }\r\n case 'combo':\r\n return {\r\n ...shared,\r\n type,\r\n dimension,\r\n bar: measure,\r\n line: previous.measures[1] ?? firstRatio ?? measure\r\n }\r\n case 'bar':\r\n return {\r\n ...shared,\r\n type,\r\n dimension,\r\n measures: previous.measures.length ? previous.measures : [measure]\r\n }\r\n case 'donut':\r\n return { ...shared, type, dimension, measure }\r\n case 'table':\r\n return {\r\n ...shared,\r\n type,\r\n dimensions: previous.dimensions.length ? previous.dimensions : [dimension],\r\n measures: previous.measures.length ? previous.measures : [measure]\r\n }\r\n }\r\n\r\n // Tipo do app: preenche os campos obrigatórios, na ordem, com o que já estava escolhido\r\n const chart: CustomChartDef = { ...shared, type }\r\n const used = { dimensions: 0, measures: 0 }\r\n for (const [key, spec] of Object.entries(fields)) {\r\n if (!spec.required || key === 'title' || key === 'span') continue\r\n switch (spec.kind) {\r\n case 'dimension':\r\n chart[key] = previous.dimensions[used.dimensions++] ?? dimension\r\n break\r\n case 'measure':\r\n chart[key] = previous.measures[used.measures++] ?? measure\r\n break\r\n case 'dimensions':\r\n chart[key] = previous.dimensions.length ? previous.dimensions : [dimension]\r\n break\r\n case 'measures':\r\n chart[key] = previous.measures.length ? previous.measures : [measure]\r\n break\r\n case 'enum':\r\n chart[key] = spec.default ?? spec.values[0]\r\n break\r\n case 'integer':\r\n chart[key] = spec.default ?? spec.min\r\n break\r\n case 'boolean':\r\n chart[key] = spec.default ?? false\r\n break\r\n case 'text':\r\n case 'color':\r\n if (spec.default !== undefined) chart[key] = spec.default\r\n break\r\n }\r\n }\r\n return chart\r\n}\r\n\r\n/** Conteúdo do arquivo `<id>.json`. */\r\nexport function layoutFileContent(layout: ChartsLayout) {\r\n return `${JSON.stringify(layout, null, 2)}\\n`\r\n}\r\n"],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as MeasureMeta, G as resolveChartTypes, J as Aggregation, K as validateLayout, M as ChartTypeSpec, P as ChartsLayout, Q as MeasureDef, W as layoutModelWarnings, X as FieldMeta, Y as DimensionDef, Z as FormulaMeasure, at as TableDef, ct as validateModel, et as ModelDef, f as DashboardResult, it as SumMeasure, nt as RatioMeasure, ot as ValueFormat, p as DashboardRow, q as AggregateMeasure, rt as RelationshipDef, st as modelMeta, u as DashboardQuery } from "../formatRules-C1PW2EiL.js";
|
|
2
2
|
//#region src/codegen.d.ts
|
|
3
3
|
/** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */
|
|
4
4
|
declare function canonicalJson(value: unknown): string;
|
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _ as reachFrom, a as toModelCode, d as modelMeta, f as validateModel, g as parseColumnRef, h as modelEdges, i as modelObjectCode, l as aggregateSql, m as measureHome, o as AGGREGATIONS, p as joinPlan, r as modelImports, t as canonicalJson, u as isAggregate, v as referencedTables } from "../codegen-LoQzFAle.js";
|
|
2
2
|
import { n as diffRows, t as describeDiffs } from "../compare-DloZhg1T.js";
|
|
3
|
-
import {
|
|
3
|
+
import { b as validateLayout, g as layoutModelWarnings, h as layoutFileContent, v as resolveChartTypes } from "../layout-BuZhfoRI.js";
|
|
4
4
|
//#region src/server/compile.ts
|
|
5
5
|
var DashboardQueryError = class extends Error {};
|
|
6
6
|
const MAX_FILTER_VALUES = 1e3;
|
|
@@ -503,6 +503,10 @@ async function handleDevtoolsRequest(context) {
|
|
|
503
503
|
rows: [],
|
|
504
504
|
devtools: { validation: await validateSql(context) }
|
|
505
505
|
};
|
|
506
|
+
if (request.testSql) return {
|
|
507
|
+
rows: [],
|
|
508
|
+
devtools: { sqlTest: await testSql(context) }
|
|
509
|
+
};
|
|
506
510
|
if (request.save) return {
|
|
507
511
|
rows: [],
|
|
508
512
|
devtools: { save: await saveModel(context) }
|
|
@@ -710,6 +714,18 @@ async function validateSql({ model, execute }) {
|
|
|
710
714
|
}
|
|
711
715
|
return validation;
|
|
712
716
|
}
|
|
717
|
+
/** Linhas da amostra de `testSql`. */
|
|
718
|
+
const TEST_ROWS = 20;
|
|
719
|
+
async function testSql({ request, execute, normalize }) {
|
|
720
|
+
const sql = String(request.testSql).trim().replace(/;+\s*$/, "");
|
|
721
|
+
if (!sql) throw new DashboardQueryError("SQL vazio");
|
|
722
|
+
const { result, durationMs } = await timed(() => execute(`SELECT * FROM (${sql}) t LIMIT ${TEST_ROWS}`, []));
|
|
723
|
+
return {
|
|
724
|
+
columns: Object.keys(result[0] ?? {}),
|
|
725
|
+
rows: result.map(normalize),
|
|
726
|
+
durationMs
|
|
727
|
+
};
|
|
728
|
+
}
|
|
713
729
|
async function saveModel({ registered, model, request, options }) {
|
|
714
730
|
if (!options.modelsDir) throw new DashboardQueryError("O endpoint não configurou `modelsDir`");
|
|
715
731
|
if (!request.model) throw new DashboardQueryError("Nada para salvar: não há rascunho");
|