@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.
@@ -1,4 +1,4 @@
1
- import { $ as ModelDef, C as BarValuePosition, E as CardStyle, L as DonutLegendPosition, M as ChartTypes, N as ChartsLayout, O as ChartDef, R as DonutValueContent, a as ValueRule, d as DashboardQueryDebug, et as ModelMeta, f as DashboardResult, g as FilterValue, i as FormatRule, j as ChartTypeSpec, k as ChartNumberFormat, l as DashboardFilters$1, n as ColumnAlign, p as DashboardRow, r as ColumnVerticalAlign, t as ColorScale, u as DashboardQuery } from "../formatRules-DxA9vMvz.js";
1
+ import { A as ChartNumberFormat, D as CardStyle, M as ChartTypeSpec, N as ChartTypes, P as ChartsLayout, R as DonutLegendPosition, a as ValueRule, d as DashboardQueryDebug, et as ModelDef, f as DashboardResult, g as FilterValue, i as FormatRule, k as ChartDef, l as DashboardFilters$1, n as ColumnAlign, p as DashboardRow, r as ColumnVerticalAlign, t as ColorScale, tt as ModelMeta, u as DashboardQuery, w as BarValuePosition, z as DonutValueContent } from "../formatRules-C1PW2EiL.js";
2
2
  import { ComponentType, MouseEvent, ReactNode } from "react";
3
3
  //#region src/client/BarChart.d.ts
4
4
  type BarChartProps = {
@@ -62,6 +62,8 @@ type BarChartProps = {
62
62
  labels?: Record<string, string>;
63
63
  /** Formato por medida (sobrepõe o dicionário do modelo). */
64
64
  formats?: Record<string, ChartNumberFormat>;
65
+ /** Dimensões cujos filtros o gráfico não respeita (além da própria, com `clickToFilter`). */
66
+ ignoreFilters?: string[];
65
67
  /** Nome da consulta no devtools. */
66
68
  queryLabel?: string;
67
69
  };
@@ -71,7 +73,7 @@ type BarChartProps = {
71
73
  * Com `series`, uma medida quebrada pelos valores de outra dimensão:
72
74
  * `<BarChart dimension="mes" series="tipo_descarga" measures={['volume']} stacked />`.
73
75
  */
74
- declare function BarChart({ dimension, measures: measureList, series, formatSeries, maxSeries, orientation, stacked, sort, limit, formatDimension, hideEmpty, showEmpty, showValues, valuePosition, valueColor, valueSize, showTotal, valueRule, hideUnselected, colors, clickToFilter, height, labels, formats: formatsByMeasure, queryLabel }: BarChartProps): import("react").JSX.Element;
76
+ declare function BarChart({ dimension, measures: measureList, series, formatSeries, maxSeries, orientation, stacked, sort, limit, formatDimension, hideEmpty, showEmpty, showValues, valuePosition, valueColor, valueSize, showTotal, valueRule, hideUnselected, colors, clickToFilter, height, labels, formats: formatsByMeasure, ignoreFilters, queryLabel }: BarChartProps): import("react").JSX.Element;
75
77
  //#endregion
76
78
  //#region src/client/ComboChart.d.ts
77
79
  type ComboChartProps = {
@@ -111,6 +113,8 @@ type ComboChartProps = {
111
113
  * destaca a seleção, como o filtro cruzado do Power BI.
112
114
  */
113
115
  clickToFilter?: boolean;
116
+ /** Dimensões cujos filtros o gráfico não respeita (além da própria, com `clickToFilter`). */
117
+ ignoreFilters?: string[];
114
118
  /** Nome da consulta no devtools (padrão: `ComboChart · <dimensão>`). */
115
119
  queryLabel?: string;
116
120
  };
@@ -118,7 +122,7 @@ type ComboChartProps = {
118
122
  * Barras + linha de uma dimensão, consultando o modelo do `DashboardProvider` (com os filtros dele).
119
123
  * Ex.: `<ComboChart dimension="mes" bar="custoTotal" line="custoTon" detail="volTotal" />`.
120
124
  */
121
- declare function ComboChart({ dimension, bar, line, detail, barLabel, lineLabel, detailLabel, formatDimension, formatBar, formatLine, formatDetail, labels, formats, hideEmpty, height, maxLabels, barColor, lineColor, clickToFilter, queryLabel }: ComboChartProps): import("react").JSX.Element;
125
+ declare function ComboChart({ dimension, bar, line, detail, barLabel, lineLabel, detailLabel, formatDimension, formatBar, formatLine, formatDetail, labels, formats, hideEmpty, height, maxLabels, barColor, lineColor, clickToFilter, ignoreFilters, queryLabel }: ComboChartProps): import("react").JSX.Element;
122
126
  //#endregion
123
127
  //#region src/client/chartKit.d.ts
124
128
  /**
@@ -202,7 +206,7 @@ type ChartPanelProps = {
202
206
  type DashboardChartsProps = {
203
207
  /** Layout importado do JSON (`src/dashboard/layouts/<id>.json`). */
204
208
  layout: ChartsLayout;
205
- /** Formatters por nome, referenciados no JSON por `formatDimension`. */
209
+ /** Formatters por nome, referenciados no JSON por `formatDimension` (além dos built-in `to_*`). */
206
210
  formatters?: Record<string, (value: FilterValue) => string>;
207
211
  /** Moldura de cada gráfico (título + conteúdo). */
208
212
  panel?: ComponentType<ChartPanelProps>;
@@ -230,6 +234,16 @@ declare function chartTitleSx(card: CardStyle): Partial<{
230
234
  fontSize: string | undefined;
231
235
  textAlign: "center" | "left" | "right" | undefined;
232
236
  }>;
237
+ /**
238
+ * `sx` da caixa que envolve o título: o espaço até o conteúdo (`contentGap`) e, com `titleDivider`,
239
+ * a linha abaixo do título e o espaço até ela (`titleGap`). Um `panel` do app põe isto na caixa do
240
+ * título para herdar divisor e espaçamentos do card (global `cards` ou por gráfico `card`).
241
+ */
242
+ declare function chartTitleBoxSx(card: CardStyle): {
243
+ mb: string | undefined;
244
+ pb?: string | undefined;
245
+ borderBottom?: string | undefined;
246
+ };
233
247
  /**
234
248
  * Renderiza os gráficos de um layout declarativo num grid de 12 colunas (no celular, um por linha;
235
249
  * dentro de um `<DashboardCanvas>` de tamanho fixo, sempre as 12). Em desenvolvimento, a aba
@@ -246,7 +260,7 @@ declare function DashboardCharts({ layout, formatters, panel: Panel, gap }: Dash
246
260
  type DashboardFiltersProps = {
247
261
  /** O mesmo layout do `<DashboardCharts>` (`filters` do JSON). */
248
262
  layout: ChartsLayout;
249
- /** Formatters por nome, referenciados no JSON por `formatLabel`. */
263
+ /** Formatters por nome, referenciados no JSON por `formatLabel` (além dos built-in `to_*`). */
250
264
  formatters?: Record<string, (value: FilterValue) => string>;
251
265
  gap?: number;
252
266
  };
@@ -376,6 +390,9 @@ type DashboardContextValue = {
376
390
  model: string;
377
391
  filters: DashboardFilters$1;
378
392
  setFilter: (dimension: string, values: FilterValue[]) => void;
393
+ /** Medida escolhida de cada parâmetro de medida do layout: `{ [name]: medida }`. */
394
+ parameters: Record<string, string>;
395
+ setParameter: (name: string, measure: string) => void;
379
396
  fetcher: DashboardFetcher;
380
397
  /** Dicionário do modelo (rótulos, descrições, formatos); `null` até carregar. */
381
398
  meta: ModelMeta | null;
@@ -474,6 +491,10 @@ type DataTableProps = {
474
491
  cellBorders?: boolean;
475
492
  /** Cor das bordas da grade. Padrão: `#e0e0e0`. */
476
493
  borderColor?: string;
494
+ /** Padding vertical das células (linhas). Padrão: `8px` (MUI default). */
495
+ cellPadding?: number | string;
496
+ /** Tamanho da fonte das células e do cabeçalho (px). Padrão: 13. */
497
+ fontSize?: number;
477
498
  /**
478
499
  * Alinhamento horizontal por coluna (`%:medida` para o percentual): cabeçalho, células e total.
479
500
  * Padrão: dimensão à esquerda, número à direita.
@@ -481,6 +502,8 @@ type DataTableProps = {
481
502
  align?: Record<string, ColumnAlign>;
482
503
  /** Alinhamento vertical por coluna (`top`, `middle`, `bottom`). Padrão: `middle`. */
483
504
  verticalAlign?: Record<string, ColumnVerticalAlign>;
505
+ /** Dimensões cujos filtros a tabela não respeita (além da 1ª, com `clickToFilter`). */
506
+ ignoreFilters?: string[];
484
507
  /** Nome da consulta no devtools. */
485
508
  queryLabel?: string;
486
509
  };
@@ -488,7 +511,7 @@ type DataTableProps = {
488
511
  * Tabela de medidas por dimensões, com total e ordenação pelo cabeçalho. Ex.:
489
512
  * `<DataTable dimensions={['navio']} measures={['volTotal', 'custoTotal', 'custoTon']} />`.
490
513
  */
491
- declare function DataTable({ dimensions, measures, percentOf, formatDimensions, showTotal, sortBy, sortDirection, clickToFilter, height, labels, formats: formatsByMeasure, percentDecimals, columnOrder, formatRules, colorScales, cellBorders, borderColor, align, verticalAlign, queryLabel }: DataTableProps): import("react").JSX.Element;
514
+ declare function DataTable({ dimensions, measures, percentOf, formatDimensions, showTotal, sortBy, sortDirection, clickToFilter, height, labels, formats: formatsByMeasure, percentDecimals, columnOrder, formatRules, colorScales, cellBorders, borderColor, align, verticalAlign, cellPadding, fontSize, ignoreFilters: ignoreProp, queryLabel }: DataTableProps): import("react").JSX.Element;
492
515
  //#endregion
493
516
  //#region src/client/DonutChart.d.ts
494
517
  type DonutChartProps = {
@@ -515,6 +538,8 @@ type DonutChartProps = {
515
538
  valueContent?: DonutValueContent;
516
539
  /** Total no furo da rosca. Padrão: `true`. */
517
540
  showTotal?: boolean;
541
+ /** Dimensões cujos filtros a rosca não respeita (além da própria, com `clickToFilter`). */
542
+ ignoreFilters?: string[];
518
543
  /** Nome da consulta no devtools. */
519
544
  queryLabel?: string;
520
545
  };
@@ -523,7 +548,7 @@ type DonutChartProps = {
523
548
  * ou em cima/embaixo; `showValues` põe o valor de cada fatia do lado de fora.
524
549
  * Ex.: `<DonutChart dimension="operacao_status" measure="custoTotal" />`.
525
550
  */
526
- declare function DonutChart({ dimension, measure, maxSlices, formatDimension, clickToFilter, height, labels, formats, percentDecimals, legendPosition, showValues, valueContent, showTotal, queryLabel }: DonutChartProps): import("react").JSX.Element;
551
+ declare function DonutChart({ dimension, measure, maxSlices, formatDimension, clickToFilter, height, labels, formats, percentDecimals, legendPosition, showValues, valueContent, showTotal, ignoreFilters, queryLabel }: DonutChartProps): import("react").JSX.Element;
527
552
  //#endregion
528
553
  //#region src/client/FilterSelect.d.ts
529
554
  type FilterSelectProps = {
@@ -558,6 +583,18 @@ declare function measureFormat(meta: ModelMeta | null, measure: string, formats?
558
583
  declare function measureFormatter(meta: ModelMeta | null, measure: string, fallbackDecimals?: number, formats?: Record<string, ChartNumberFormat>): (value: number | string | null | undefined) => string;
559
584
  /** Rótulo de uma dimensão/medida: o `labels` do gráfico, o dicionário do modelo ou o nome. */
560
585
  declare function fieldLabel(meta: ModelMeta | null, name: string, labels?: Record<string, string>): string;
586
+ /** `3` → "Março"; aceita também uma data ISO (usa o mês dela). Fora de 1–12, o próprio valor. */
587
+ declare function toMonthName(value: FilterValue): string;
588
+ /**
589
+ * Data ISO → `dd/mm/aaaa`. Só a data (`2026-10-01`) não passa por fuso; com hora (o `Date` do
590
+ * banco chega como ISO em UTC), vira o horário local e a hora só aparece quando não é meia-noite.
591
+ */
592
+ declare function toDate(value: FilterValue): string;
593
+ /**
594
+ * Formatters que todo `<DashboardCharts>`/`<DashboardFilters>` já tem, pelo nome (os do app com o
595
+ * mesmo nome vão por cima).
596
+ */
597
+ declare const BUILTIN_FORMATTERS: Record<string, (value: FilterValue) => string>;
561
598
  //#endregion
562
599
  //#region src/client/KpiCard.d.ts
563
600
  type KpiCardProps = {
@@ -573,6 +610,8 @@ type KpiCardProps = {
573
610
  labels?: Record<string, string>;
574
611
  /** Formato por medida (sobrepõe o dicionário). */
575
612
  formats?: Record<string, ChartNumberFormat>;
613
+ /** Dimensões cujos filtros o KPI não respeita (ex.: um total que ignora um slicer). */
614
+ ignoreFilters?: string[];
576
615
  /** Nome da consulta no devtools. */
577
616
  queryLabel?: string;
578
617
  };
@@ -581,7 +620,7 @@ type KpiCardProps = {
581
620
  * dimensões: razões (custo/t) saem do total, não de uma média. Ex.:
582
621
  * `<KpiCard label="Custo total" measure="custoTotal" secondary="custoTon" />`.
583
622
  */
584
- declare function KpiCard({ measure, label, secondary, secondaryLabel, labels, formats, queryLabel }: KpiCardProps): import("react").JSX.Element;
623
+ declare function KpiCard({ measure, label, secondary, secondaryLabel, labels, formats, ignoreFilters, queryLabel }: KpiCardProps): import("react").JSX.Element;
585
624
  //#endregion
586
625
  //#region src/client/ModelDictionary.d.ts
587
626
  /**
@@ -617,5 +656,5 @@ declare function useModelQuery({ dimensions, measures, ignoreFilters, label, ski
617
656
  };
618
657
  };
619
658
  //#endregion
620
- export { BarChart, type ChartComponentProps, type ChartComponentSpec, type ChartPanelProps, ComboChart, DashboardCanvas, DashboardCharts, type DashboardFetcher, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartCardSx, chartPlaceholder, chartTitleSx, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
659
+ export { BUILTIN_FORMATTERS, BarChart, type ChartComponentProps, type ChartComponentSpec, type ChartPanelProps, ComboChart, DashboardCanvas, DashboardCharts, type DashboardFetcher, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartCardSx, chartPlaceholder, chartTitleBoxSx, chartTitleSx, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, toDate, toMonthName, useClickFilter, useDashboard, useModelQuery };
621
660
  //# sourceMappingURL=index.d.ts.map
@@ -1,8 +1,8 @@
1
- import { A as contrastText, M as rowRuleStyles, d as customChartProps, g as percentColumn, j as matchesValueRule, k as colorScaleStyles, o as cardsGap, p as isBuiltinChart, s as chartCardStyle, v as tableColumns } from "../layout-BS3nO6OT.js";
2
- import { S as layoutItemKey, _ as useClickFilter, a as formatCurrency, c as measureFormat, d as OTHER_COLOR, f as SERIES_COLORS, g as seriesColor, h as numberOf, i as fieldLabel, l as measureFormatter, m as chartPlaceholder, n as INSPECT_ATTRIBUTE, o as formatMeasure, p as TooltipBox, r as useModelQuery, s as formatNumber, t as ModelDictionary, u as Legend, v as DashboardProvider, x as LAYOUT_ITEM_ATTRIBUTE, y as useDashboard } from "../ModelDictionary-Cw4sa8tW.js";
1
+ import { A as colorScaleStyles, M as matchesValueRule, N as rowRuleStyles, _ as percentColumn, c as chartCardStyle, f as customChartProps, j as contrastText, m as isBuiltinChart, s as cardsGap, y as tableColumns } from "../layout-BuZhfoRI.js";
2
+ import { C as useDashboard, E as layoutItemKey, S as DashboardProvider, T as LAYOUT_ITEM_ATTRIBUTE, _ as TooltipBox, a as fieldLabel, b as seriesColor, c as formatNumber, d as toDate, f as toMonthName, g as SERIES_COLORS, h as OTHER_COLOR, i as BUILTIN_FORMATTERS, l as measureFormat, m as Legend, n as INSPECT_ATTRIBUTE, o as formatCurrency, p as withBuiltinFormatters, r as useModelQuery, s as formatMeasure, t as ModelDictionary, u as measureFormatter, v as chartPlaceholder, x as useClickFilter, y as numberOf } from "../ModelDictionary-g5d84D0n.js";
3
3
  import { Box, Checkbox, CircularProgress, FormControl, InputLabel, ListItemText, MenuItem, OutlinedInput, Paper, Select, Table, TableBody, TableCell, TableHead, TableRow, TableSortLabel, Typography } from "@mui/material";
4
4
  import { Bar, BarChart as BarChart$1, CartesianGrid, Cell, ComposedChart, LabelList, Line, Pie, PieChart, ResponsiveContainer, Tooltip as Tooltip$1, XAxis, YAxis } from "recharts";
5
- import { createContext, useContext, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
5
+ import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/client/seriesPivot.ts
8
8
  const EMPTY_SERIES_LABEL = "(em branco)";
@@ -134,14 +134,15 @@ const ROW_HEIGHT = 28;
134
134
  * Com `series`, uma medida quebrada pelos valores de outra dimensão:
135
135
  * `<BarChart dimension="mes" series="tipo_descarga" measures={['volume']} stacked />`.
136
136
  */
137
- function BarChart({ dimension, measures: measureList, series, formatSeries = String, maxSeries = 6, orientation = "vertical", stacked = false, sort = "dimension", limit, formatDimension = String, hideEmpty = true, showEmpty = false, showValues = true, valuePosition = "outside", valueColor, valueSize = 12, showTotal = false, valueRule, hideUnselected = false, colors, clickToFilter = false, height = 360, labels, formats: formatsByMeasure, queryLabel }) {
137
+ function BarChart({ dimension, measures: measureList, series, formatSeries = String, maxSeries = 6, orientation = "vertical", stacked = false, sort = "dimension", limit, formatDimension = String, hideEmpty = true, showEmpty = false, showValues = true, valuePosition = "outside", valueColor, valueSize = 12, showTotal = false, valueRule, hideUnselected = false, colors, clickToFilter = false, height = 360, labels, formats: formatsByMeasure, ignoreFilters, queryLabel }) {
138
138
  const { meta, filters, setFilter } = useDashboard();
139
139
  const dimming = clickToFilter && !hideUnselected;
140
+ const configuredIgnore = ignoreFilters ?? [];
140
141
  const measures = series ? measureList.slice(0, 1) : measureList;
141
142
  const { rows, isLoading, error, inspectProps } = useModelQuery({
142
143
  dimensions: series ? [dimension, series] : [dimension],
143
144
  measures,
144
- ignoreFilters: dimming ? series ? [dimension, series] : [dimension] : void 0,
145
+ ignoreFilters: [...configuredIgnore, ...dimming ? series ? [dimension, series] : [dimension] : []],
145
146
  label: queryLabel ?? `BarChart · ${dimension}`
146
147
  });
147
148
  const domain = useModelQuery({
@@ -154,7 +155,7 @@ function BarChart({ dimension, measures: measureList, series, formatSeries = Str
154
155
  const table = meta?.dimensions[dimension]?.table;
155
156
  const categories = useModelQuery({
156
157
  dimensions: [dimension],
157
- ignoreFilters: Object.keys(filters).filter((name) => dimming && name === dimension || !table || meta?.dimensions[name]?.table !== table),
158
+ ignoreFilters: Object.keys(filters).filter((name) => configuredIgnore.includes(name) || dimming && name === dimension || !table || meta?.dimensions[name]?.table !== table),
158
159
  label: `${queryLabel ?? `BarChart · ${dimension}`} · categorias`,
159
160
  skip: !showEmpty || !meta
160
161
  });
@@ -454,8 +455,9 @@ const lineDomain = ([min, max]) => {
454
455
  * Barras + linha de uma dimensão, consultando o modelo do `DashboardProvider` (com os filtros dele).
455
456
  * Ex.: `<ComboChart dimension="mes" bar="custoTotal" line="custoTon" detail="volTotal" />`.
456
457
  */
457
- function ComboChart({ dimension, bar, line, detail, barLabel, lineLabel, detailLabel, formatDimension = String, formatBar, formatLine, formatDetail, labels, formats, hideEmpty = true, height = 360, maxLabels = 24, barColor = "#F58220", lineColor = "#555", clickToFilter = false, queryLabel }) {
458
+ function ComboChart({ dimension, bar, line, detail, barLabel, lineLabel, detailLabel, formatDimension = String, formatBar, formatLine, formatDetail, labels, formats, hideEmpty = true, height = 360, maxLabels = 24, barColor = "#F58220", lineColor = "#555", clickToFilter = false, ignoreFilters, queryLabel }) {
458
459
  const { meta, filters, setFilter } = useDashboard();
460
+ const ignore = [...ignoreFilters ?? [], ...clickToFilter ? [dimension] : []];
459
461
  const fromModel = (measure, fallback) => {
460
462
  const format = measureFormat(meta, measure, formats);
461
463
  const resolved = format?.format ? format : {
@@ -481,7 +483,7 @@ function ComboChart({ dimension, bar, line, detail, barLabel, lineLabel, detailL
481
483
  line,
482
484
  detail
483
485
  ] : [bar, line],
484
- ignoreFilters: clickToFilter ? [dimension] : void 0,
486
+ ignoreFilters: ignore.length ? ignore : void 0,
485
487
  label: queryLabel ?? `ComboChart · ${dimension}`
486
488
  });
487
489
  const num = (row, key) => key && row[key] !== null && row[key] !== void 0 ? Number(row[key]) : null;
@@ -828,11 +830,12 @@ function compare(a, b) {
828
830
  * Tabela de medidas por dimensões, com total e ordenação pelo cabeçalho. Ex.:
829
831
  * `<DataTable dimensions={['navio']} measures={['volTotal', 'custoTotal', 'custoTon']} />`.
830
832
  */
831
- function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDimensions = {}, showTotal = true, sortBy, sortDirection = "desc", clickToFilter = false, height = 400, labels, formats: formatsByMeasure, percentDecimals = 1, columnOrder, formatRules = NO_RULES, colorScales = NO_SCALES, cellBorders = false, borderColor = "#e0e0e0", align, verticalAlign, queryLabel }) {
833
+ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDimensions = {}, showTotal = true, sortBy, sortDirection = "desc", clickToFilter = false, height = 400, labels, formats: formatsByMeasure, percentDecimals = 1, columnOrder, formatRules = NO_RULES, colorScales = NO_SCALES, cellBorders = false, borderColor = "#e0e0e0", align, verticalAlign, cellPadding, fontSize = 13, ignoreFilters: ignoreProp = EMPTY, queryLabel }) {
832
834
  const { meta } = useDashboard();
833
835
  const name = queryLabel ?? `DataTable · ${dimensions.join(", ") || "total"}`;
834
836
  const firstDimension = dimensions[0];
835
- const ignoreFilters = clickToFilter && firstDimension ? [firstDimension] : void 0;
837
+ const ignore = [...ignoreProp, ...clickToFilter && firstDimension ? [firstDimension] : []];
838
+ const ignoreFilters = ignore.length ? ignore : void 0;
836
839
  const extraMeasures = percentOf.filter((measure) => !measures.includes(measure));
837
840
  const outside = [...new Set([sortBy, ...formatRules.map((rule) => rule.field)].filter(Boolean))].filter((field) => !field.startsWith("%:") && !dimensions.includes(field) && !measures.includes(field) && !extraMeasures.includes(field));
838
841
  const waitingMeta = outside.length > 0 && !meta;
@@ -928,6 +931,8 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
928
931
  const verticalOf = (column) => verticalAlign?.[column] ?? "middle";
929
932
  const totalLabelColumn = columns.find(isDimension);
930
933
  const cellText = (row, column) => column.startsWith("%:") ? formatPercent(percentOfRow(row, column.slice(2))) : formats[column]?.(row[column]);
934
+ const cellPaddingValue = cellPadding ?? 8;
935
+ const cellPaddingStyle = typeof cellPaddingValue === "number" ? `${cellPaddingValue}px` : cellPaddingValue;
931
936
  const toggleSort = (column) => setSort((current) => current.by === column ? {
932
937
  by: column,
933
938
  direction: current.direction === "asc" ? "desc" : "asc"
@@ -983,7 +988,7 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
983
988
  size: "small",
984
989
  stickyHeader: true,
985
990
  sx: { "& td, & th": {
986
- fontSize: 13,
991
+ fontSize,
987
992
  ...cellBorders && { border: `1px solid ${borderColor}` }
988
993
  } },
989
994
  children: [/* @__PURE__ */ jsx(TableHead, { children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((column) => header(column, !isDimension(column))) }) }), /* @__PURE__ */ jsxs(TableBody, { children: [sorted.map((row) => {
@@ -1002,6 +1007,8 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
1002
1007
  align: alignOf(column),
1003
1008
  sx: {
1004
1009
  verticalAlign: verticalOf(column),
1010
+ paddingTop: cellPaddingStyle,
1011
+ paddingBottom: cellPaddingStyle,
1005
1012
  ...ruleSx(styles?.[column])
1006
1013
  },
1007
1014
  children: dimensionText(column, row[column])
@@ -1010,6 +1017,8 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
1010
1017
  sx: {
1011
1018
  whiteSpace: "nowrap",
1012
1019
  verticalAlign: verticalOf(column),
1020
+ paddingTop: cellPaddingStyle,
1021
+ paddingBottom: cellPaddingStyle,
1013
1022
  ...ruleSx(styles?.[column])
1014
1023
  },
1015
1024
  children: cellText(row, column)
@@ -1025,13 +1034,19 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
1025
1034
  } },
1026
1035
  children: columns.map((column) => isDimension(column) ? /* @__PURE__ */ jsx(TableCell, {
1027
1036
  align: alignOf(column),
1028
- sx: { verticalAlign: verticalOf(column) },
1037
+ sx: {
1038
+ verticalAlign: verticalOf(column),
1039
+ paddingTop: cellPaddingStyle,
1040
+ paddingBottom: cellPaddingStyle
1041
+ },
1029
1042
  children: column === totalLabelColumn ? "Total" : ""
1030
1043
  }, column) : /* @__PURE__ */ jsx(TableCell, {
1031
1044
  align: alignOf(column),
1032
1045
  sx: {
1033
1046
  whiteSpace: "nowrap",
1034
- verticalAlign: verticalOf(column)
1047
+ verticalAlign: verticalOf(column),
1048
+ paddingTop: cellPaddingStyle,
1049
+ paddingBottom: cellPaddingStyle
1035
1050
  },
1036
1051
  children: column.startsWith("%:") ? formatPercent(sums[column.slice(2)] ? 1 : null) : formats[column]?.(totalRow[column])
1037
1052
  }, column))
@@ -1058,12 +1073,13 @@ const LEGEND_DIRECTION = {
1058
1073
  * ou em cima/embaixo; `showValues` põe o valor de cada fatia do lado de fora.
1059
1074
  * Ex.: `<DonutChart dimension="operacao_status" measure="custoTotal" />`.
1060
1075
  */
1061
- function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = String, clickToFilter = false, height = 300, labels, formats, percentDecimals = 1, legendPosition = "right", showValues = false, valueContent = "both", showTotal = true, queryLabel }) {
1076
+ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = String, clickToFilter = false, height = 300, labels, formats, percentDecimals = 1, legendPosition = "right", showValues = false, valueContent = "both", showTotal = true, ignoreFilters, queryLabel }) {
1062
1077
  const { meta } = useDashboard();
1078
+ const ignore = [...ignoreFilters ?? [], ...clickToFilter ? [dimension] : []];
1063
1079
  const { rows, isLoading, error, inspectProps } = useModelQuery({
1064
1080
  dimensions: [dimension],
1065
1081
  measures: [measure],
1066
- ignoreFilters: clickToFilter ? [dimension] : void 0,
1082
+ ignoreFilters: ignore.length ? ignore : void 0,
1067
1083
  label: queryLabel ?? `DonutChart · ${dimension}`
1068
1084
  });
1069
1085
  const filter = useClickFilter(dimension, clickToFilter);
@@ -1318,10 +1334,11 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
1318
1334
  * dimensões: razões (custo/t) saem do total, não de uma média. Ex.:
1319
1335
  * `<KpiCard label="Custo total" measure="custoTotal" secondary="custoTon" />`.
1320
1336
  */
1321
- function KpiCard({ measure, label, secondary, secondaryLabel, labels, formats, queryLabel }) {
1337
+ function KpiCard({ measure, label, secondary, secondaryLabel, labels, formats, ignoreFilters, queryLabel }) {
1322
1338
  const { meta } = useDashboard();
1323
1339
  const { rows, isLoading, error, inspectProps } = useModelQuery({
1324
1340
  measures: secondary ? [measure, secondary] : [measure],
1341
+ ignoreFilters: ignoreFilters?.length ? ignoreFilters : void 0,
1325
1342
  label: queryLabel ?? `KpiCard · ${measure}`
1326
1343
  });
1327
1344
  const row = rows[0];
@@ -1422,6 +1439,20 @@ function chartTitleSx(card) {
1422
1439
  textAlign: card.titleAlign
1423
1440
  });
1424
1441
  }
1442
+ /**
1443
+ * `sx` da caixa que envolve o título: o espaço até o conteúdo (`contentGap`) e, com `titleDivider`,
1444
+ * a linha abaixo do título e o espaço até ela (`titleGap`). Um `panel` do app põe isto na caixa do
1445
+ * título para herdar divisor e espaçamentos do card (global `cards` ou por gráfico `card`).
1446
+ */
1447
+ function chartTitleBoxSx(card) {
1448
+ return {
1449
+ mb: px(card.contentGap ?? 8),
1450
+ ...card.titleDivider && {
1451
+ pb: px(card.titleGap ?? 4),
1452
+ borderBottom: `1px solid ${card.titleDividerColor ?? "#e0e0e0"}`
1453
+ }
1454
+ };
1455
+ }
1425
1456
  function DefaultPanel({ title, card, children }) {
1426
1457
  return /* @__PURE__ */ jsxs(Paper, {
1427
1458
  elevation: 0,
@@ -1433,13 +1464,15 @@ function DefaultPanel({ title, card, children }) {
1433
1464
  borderColor: "divider",
1434
1465
  ...chartCardSx(card)
1435
1466
  },
1436
- children: [title && /* @__PURE__ */ jsx(Typography, {
1437
- sx: {
1438
- fontWeight: 700,
1439
- mb: 1,
1440
- ...chartTitleSx(card)
1441
- },
1442
- children: title
1467
+ children: [title && /* @__PURE__ */ jsx(Box, {
1468
+ sx: chartTitleBoxSx(card),
1469
+ children: /* @__PURE__ */ jsx(Typography, {
1470
+ sx: {
1471
+ fontWeight: 700,
1472
+ ...chartTitleSx(card)
1473
+ },
1474
+ children: title
1475
+ })
1443
1476
  }), children]
1444
1477
  });
1445
1478
  }
@@ -1454,8 +1487,32 @@ function DefaultPanel({ title, card, children }) {
1454
1487
  * ```
1455
1488
  */
1456
1489
  function DashboardCharts({ layout, formatters = {}, panel: Panel = DefaultPanel, gap = 1.5 }) {
1490
+ formatters = withBuiltinFormatters(formatters);
1457
1491
  const current = useLayout(layout, "charts", formatters);
1458
1492
  const { charts } = current;
1493
+ const { parameters, meta } = useDashboard();
1494
+ const ignoredByFilter = useMemo(() => {
1495
+ const map = /* @__PURE__ */ new Map();
1496
+ for (const filter of current.filters ?? []) for (const id of filter.ignoredBy ?? []) map.set(id, [...map.get(id) ?? [], filter.dimension]);
1497
+ return map;
1498
+ }, [current.filters]);
1499
+ const measureOverride = useMemo(() => {
1500
+ const map = /* @__PURE__ */ new Map();
1501
+ const byName = new Map((current.parameters ?? []).map((parameter) => [parameter.name, parameter]));
1502
+ for (const chart of charts) {
1503
+ const linked = chart.measureParameter;
1504
+ if (typeof linked !== "string") continue;
1505
+ const parameter = byName.get(linked);
1506
+ if (!parameter) continue;
1507
+ const selected = parameters[parameter.name] ?? parameter.default ?? parameter.measures[0];
1508
+ if (selected) map.set(chart.id, selected);
1509
+ }
1510
+ return map;
1511
+ }, [
1512
+ current.parameters,
1513
+ parameters,
1514
+ charts
1515
+ ]);
1459
1516
  const fixed = useFixedCanvas();
1460
1517
  const columns = `repeat(12, minmax(0, 1fr))`;
1461
1518
  const space = cardsGap(current.cards);
@@ -1469,27 +1526,64 @@ function DashboardCharts({ layout, formatters = {}, panel: Panel = DefaultPanel,
1469
1526
  lg: columns
1470
1527
  }
1471
1528
  },
1472
- children: charts.map((chart) => /* @__PURE__ */ jsx(Box, {
1473
- [LAYOUT_ITEM_ATTRIBUTE]: layoutItemKey(layout.id, {
1474
- kind: "chart",
1475
- key: chart.id
1476
- }),
1477
- sx: {
1478
- minWidth: 0,
1479
- gridColumn: fixed ? `span ${chart.span ?? 12}` : { lg: `span ${chart.span ?? 12}` }
1480
- },
1481
- children: /* @__PURE__ */ jsx(Panel, {
1482
- title: chart.title,
1483
- chart,
1484
- card: chartCardStyle(current, chart),
1485
- children: /* @__PURE__ */ jsx(Chart, {
1529
+ children: charts.map((chart) => {
1530
+ const override = measureOverride.get(chart.id);
1531
+ const effective = withMeasure(withFilterIgnores(chart, ignoredByFilter.get(chart.id)), override);
1532
+ const measure = override ?? chart.measure;
1533
+ const title = resolveTitle(chart.title, typeof measure === "string" ? measure : void 0, meta);
1534
+ return /* @__PURE__ */ jsx(Box, {
1535
+ [LAYOUT_ITEM_ATTRIBUTE]: layoutItemKey(layout.id, {
1536
+ kind: "chart",
1537
+ key: chart.id
1538
+ }),
1539
+ sx: {
1540
+ minWidth: 0,
1541
+ gridColumn: fixed ? `span ${chart.span ?? 12}` : { lg: `span ${chart.span ?? 12}` }
1542
+ },
1543
+ children: /* @__PURE__ */ jsx(Panel, {
1544
+ title,
1486
1545
  chart,
1487
- formatters
1546
+ card: chartCardStyle(current, chart),
1547
+ children: /* @__PURE__ */ jsx(Chart, {
1548
+ chart: effective,
1549
+ formatters
1550
+ })
1488
1551
  })
1489
- })
1490
- }, chart.id))
1552
+ }, chart.id);
1553
+ })
1491
1554
  });
1492
1555
  }
1556
+ /**
1557
+ * Soma ao `ignoreFilters` do gráfico as dimensões dos filtros que o marcaram em `ignoredBy`, sem
1558
+ * duplicar. Vazio = devolve o gráfico como está (sem recriar o objeto).
1559
+ */
1560
+ function withFilterIgnores(chart, fromFilters) {
1561
+ if (!fromFilters?.length) return chart;
1562
+ const ignoreFilters = [.../* @__PURE__ */ new Set([...chart.ignoreFilters ?? [], ...fromFilters])];
1563
+ return {
1564
+ ...chart,
1565
+ ignoreFilters
1566
+ };
1567
+ }
1568
+ /** Troca a medida do gráfico pela escolhida num parâmetro de medida (vazio = gráfico como está). */
1569
+ function withMeasure(chart, measure) {
1570
+ if (!measure || chart.measure === measure) return chart;
1571
+ return {
1572
+ ...chart,
1573
+ measure
1574
+ };
1575
+ }
1576
+ const TITLE_TOKEN = /\{\{\s*medida\s*\}\}/g;
1577
+ /**
1578
+ * Resolve `{{medida}}` no título para a descrição da medida atual do gráfico (ou o rótulo, se não
1579
+ * houver descrição; vazio, se o gráfico não tem medida única). Com um parâmetro de medida, a medida
1580
+ * atual muda com o slicer, então o título acompanha. Sem token, devolve o título como está.
1581
+ */
1582
+ function resolveTitle(title, measure, meta) {
1583
+ if (!title.includes("{{")) return title;
1584
+ const name = measure ? meta?.measures[measure]?.description || fieldLabel(meta, measure) : "";
1585
+ return title.replace(TITLE_TOKEN, name);
1586
+ }
1493
1587
  function Chart({ chart, formatters }) {
1494
1588
  const { chartTypes, chartComponents } = useDashboard();
1495
1589
  if (!isBuiltinChart(chart)) {
@@ -1617,20 +1711,55 @@ function FilterSelect({ dimension, label, formatLabel = String, width = 200 }) {
1617
1711
  });
1618
1712
  }
1619
1713
  //#endregion
1714
+ //#region src/client/MeasureSelect.tsx
1715
+ /**
1716
+ * Slicer de um parâmetro de medida ("medida dinâmica"): escolhe UMA medida entre `measures`. A
1717
+ * escolha fica no `DashboardProvider` (`parameters[name]`) e o `<DashboardCharts>` troca a medida
1718
+ * dos gráficos de medida única que seguem o parâmetro. Os rótulos vêm do dicionário do modelo.
1719
+ */
1720
+ function MeasureSelect({ name, measures, label = "Medida", default: fallback, width = 200 }) {
1721
+ const { meta, parameters, setParameter } = useDashboard();
1722
+ const selected = parameters[name] ?? fallback ?? measures[0];
1723
+ if (!selected) return null;
1724
+ return /* @__PURE__ */ jsxs(FormControl, {
1725
+ size: "small",
1726
+ sx: { width },
1727
+ children: [/* @__PURE__ */ jsx(InputLabel, {
1728
+ shrink: true,
1729
+ children: label
1730
+ }), /* @__PURE__ */ jsx(Select, {
1731
+ value: selected,
1732
+ onChange: (event) => setParameter(name, event.target.value),
1733
+ input: /* @__PURE__ */ jsx(OutlinedInput, {
1734
+ notched: true,
1735
+ label
1736
+ }),
1737
+ MenuProps: { slotProps: { paper: { sx: { maxHeight: 360 } } } },
1738
+ sx: { bgcolor: "#fff" },
1739
+ children: measures.map((measure) => /* @__PURE__ */ jsx(MenuItem, {
1740
+ value: measure,
1741
+ dense: true,
1742
+ children: fieldLabel(meta, measure)
1743
+ }, measure))
1744
+ })]
1745
+ });
1746
+ }
1747
+ //#endregion
1620
1748
  //#region src/client/DashboardFilters.tsx
1621
1749
  /**
1622
1750
  * Filtros (slicers) declarados em `layout.filters`, na ordem. Em desenvolvimento, a aba Layout do
1623
1751
  * devtools adiciona, remove, reordena e edita os filtros ao vivo.
1624
1752
  */
1625
1753
  function DashboardFilters({ layout, formatters = {}, gap = 2 }) {
1626
- const { filters = [] } = useLayout(layout, "filters", formatters);
1627
- return /* @__PURE__ */ jsx(Box, {
1754
+ formatters = withBuiltinFormatters(formatters);
1755
+ const { filters = [], parameters = [] } = useLayout(layout, "filters", formatters);
1756
+ return /* @__PURE__ */ jsxs(Box, {
1628
1757
  sx: {
1629
1758
  display: "flex",
1630
1759
  flexWrap: "wrap",
1631
1760
  gap
1632
1761
  },
1633
- children: filters.map((filter) => /* @__PURE__ */ jsx(Box, {
1762
+ children: [filters.map((filter) => /* @__PURE__ */ jsx(Box, {
1634
1763
  [LAYOUT_ITEM_ATTRIBUTE]: layoutItemKey(layout.id, {
1635
1764
  kind: "filter",
1636
1765
  key: filter.dimension
@@ -1641,10 +1770,22 @@ function DashboardFilters({ layout, formatters = {}, gap = 2 }) {
1641
1770
  width: filter.width,
1642
1771
  formatLabel: filter.formatLabel ? formatters[filter.formatLabel] : void 0
1643
1772
  })
1644
- }, filter.dimension))
1773
+ }, filter.dimension)), parameters.map((parameter) => /* @__PURE__ */ jsx(Box, {
1774
+ [LAYOUT_ITEM_ATTRIBUTE]: layoutItemKey(layout.id, {
1775
+ kind: "parameter",
1776
+ key: parameter.name
1777
+ }),
1778
+ children: /* @__PURE__ */ jsx(MeasureSelect, {
1779
+ name: parameter.name,
1780
+ measures: parameter.measures,
1781
+ label: parameter.label,
1782
+ default: parameter.default,
1783
+ width: parameter.width
1784
+ })
1785
+ }, `param:${parameter.name}`))]
1645
1786
  });
1646
1787
  }
1647
1788
  //#endregion
1648
- export { BarChart, ComboChart, DashboardCanvas, DashboardCharts, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartCardSx, chartPlaceholder, chartTitleSx, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
1789
+ export { BUILTIN_FORMATTERS, BarChart, ComboChart, DashboardCanvas, DashboardCharts, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartCardSx, chartPlaceholder, chartTitleBoxSx, chartTitleSx, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, toDate, toMonthName, useClickFilter, useDashboard, useModelQuery };
1649
1790
 
1650
1791
  //# sourceMappingURL=index.js.map