@bluemarble/bi 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
- import { f as isBuiltinChart, o as chartCardStyle, u as customChartProps } from "../layout-DTR4vI-I.js";
2
- import { a as useDashboard, c as layoutItemKey, i as DashboardProvider, n as INSPECT_ATTRIBUTE, r as useModelQuery, s as LAYOUT_ITEM_ATTRIBUTE, t as ModelDictionary } from "../ModelDictionary-kUKcdbc8.js";
1
+ import { f as isBuiltinChart, h as orderedColumns, o as chartCardStyle, u as customChartProps } from "../layout-QuOYFmp1.js";
2
+ import { a as formatCurrency, c as measureFormat, d as useDashboard, i as fieldLabel, l as measureFormatter, m as layoutItemKey, n as INSPECT_ATTRIBUTE, o as formatMeasure, p as LAYOUT_ITEM_ATTRIBUTE, r as useModelQuery, s as formatNumber, t as ModelDictionary, u as DashboardProvider } from "../ModelDictionary-D3JzCd6x.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
5
  import { createContext, useContext, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
@@ -95,7 +95,8 @@ function chartPlaceholder({ height, error, isLoading, empty }) {
95
95
  children: error ? "Erro ao carregar o gráfico" : isLoading ? "Carregando…" : "Sem dados para os filtros selecionados"
96
96
  });
97
97
  }
98
- function TooltipBox({ title, children }) {
98
+ /** `caption`: o que é o título (ex.: o rótulo da dimensão), em cinza acima dele. */
99
+ function TooltipBox({ title, caption, children }) {
99
100
  return /* @__PURE__ */ jsxs(Box, {
100
101
  sx: {
101
102
  bgcolor: "#fff",
@@ -103,55 +104,23 @@ function TooltipBox({ title, children }) {
103
104
  p: 1,
104
105
  fontSize: 13
105
106
  },
106
- children: [/* @__PURE__ */ jsx(Box, {
107
- sx: { fontWeight: 700 },
108
- children: title
109
- }), children]
107
+ children: [
108
+ caption && /* @__PURE__ */ jsx(Box, {
109
+ sx: {
110
+ fontSize: 11,
111
+ color: "#777"
112
+ },
113
+ children: caption
114
+ }),
115
+ /* @__PURE__ */ jsx(Box, {
116
+ sx: { fontWeight: 700 },
117
+ children: title
118
+ }),
119
+ children
120
+ ]
110
121
  });
111
122
  }
112
123
  //#endregion
113
- //#region src/client/format.ts
114
- const formatters = /* @__PURE__ */ new Map();
115
- function formatNumber(value, decimals = 2) {
116
- if (value === null || value === void 0 || value === "") return "";
117
- const number = Number(value);
118
- if (Number.isNaN(number)) return String(value);
119
- let formatter = formatters.get(decimals);
120
- if (!formatter) {
121
- formatter = new Intl.NumberFormat("pt-BR", {
122
- minimumFractionDigits: decimals,
123
- maximumFractionDigits: decimals
124
- });
125
- formatters.set(decimals, formatter);
126
- }
127
- return formatter.format(number);
128
- }
129
- const formatCurrency = (value, decimals = 2) => {
130
- const text = formatNumber(value, decimals);
131
- return text && `R$ ${text}`;
132
- };
133
- /**
134
- * Formata pelo dicionário do modelo (`format`/`decimals` da medida). `fallbackDecimals` é o padrão
135
- * do componente quando a medida não define `decimals`.
136
- */
137
- function formatMeasure(value, meta, fallbackDecimals = 2) {
138
- const decimals = meta?.decimals ?? (meta?.format === "integer" ? 0 : fallbackDecimals);
139
- switch (meta?.format) {
140
- case "currency": return formatCurrency(value, decimals);
141
- case "percent": {
142
- if (value === null || value === void 0 || value === "") return "";
143
- const text = formatNumber(Number(value) * 100, decimals);
144
- return text && `${text}%`;
145
- }
146
- default: return formatNumber(value, decimals);
147
- }
148
- }
149
- /** Formatter de uma medida pelo dicionário do modelo; sem `format`, número com `fallbackDecimals`. */
150
- function measureFormatter(meta, measure, fallbackDecimals = 2) {
151
- const measureMeta = meta?.measures[measure];
152
- return (value) => formatMeasure(value, measureMeta, fallbackDecimals);
153
- }
154
- //#endregion
155
124
  //#region src/client/BarChart.tsx
156
125
  const MAX_VALUE_LABELS = 30;
157
126
  /** Altura de cada categoria no modo horizontal (barras finas, com respiro). */
@@ -160,7 +129,7 @@ const ROW_HEIGHT = 28;
160
129
  * Barras de uma ou mais medidas por uma dimensão. Ex.:
161
130
  * `<BarChart dimension="navio" measures={['custoTotal']} orientation="horizontal" sort="desc" limit={10} />`.
162
131
  */
163
- function BarChart({ dimension, measures, orientation = "vertical", stacked = false, sort = "dimension", limit, formatDimension = String, hideEmpty = true, showValues = true, clickToFilter = false, height = 360, queryLabel }) {
132
+ function BarChart({ dimension, measures, orientation = "vertical", stacked = false, sort = "dimension", limit, formatDimension = String, hideEmpty = true, showValues = true, clickToFilter = false, height = 360, labels, formats: formatsByMeasure, queryLabel }) {
164
133
  const { meta } = useDashboard();
165
134
  const { rows, isLoading, error, inspectProps } = useModelQuery({
166
135
  dimensions: [dimension],
@@ -169,8 +138,13 @@ function BarChart({ dimension, measures, orientation = "vertical", stacked = fal
169
138
  label: queryLabel ?? `BarChart · ${dimension}`
170
139
  });
171
140
  const filter = useClickFilter(dimension, clickToFilter);
172
- const formats = measures.map((measure) => measureFormatter(meta, measure, 0));
173
- const labelOf = (measure) => meta?.measures[measure]?.label ?? measure;
141
+ const formats = measures.map((measure) => measureFormatter(meta, measure, 0, formatsByMeasure));
142
+ const labelOf = (name) => fieldLabel(meta, name, labels);
143
+ const axisFormat = measures[0] ? measureFormat(meta, measures[0], formatsByMeasure) : void 0;
144
+ const formatTick = (value) => formatMeasure(value, {
145
+ ...axisFormat,
146
+ decimals: axisFormat?.compact ? 1 : 0
147
+ }, 0);
174
148
  const first = measures[0];
175
149
  let data = rows.filter((row) => !hideEmpty || measures.some((measure) => numberOf(row, measure))).map((row) => {
176
150
  const value = row[dimension] ?? "";
@@ -184,7 +158,7 @@ function BarChart({ dimension, measures, orientation = "vertical", stacked = fal
184
158
  if (sort !== "dimension" && first) data = [...data].sort((a, b) => sort === "desc" ? Number(b[first]) - Number(a[first]) : Number(a[first]) - Number(b[first]));
185
159
  if (limit) data = data.slice(0, limit);
186
160
  const horizontal = orientation === "horizontal";
187
- const labels = showValues && data.length <= MAX_VALUE_LABELS;
161
+ const showLabels = showValues && data.length <= MAX_VALUE_LABELS;
188
162
  const chartHeight = horizontal ? Math.max(height, data.length * ROW_HEIGHT + 24) : height;
189
163
  const longest = Math.max(0, ...data.map((point) => point.label.length));
190
164
  const state = chartPlaceholder({
@@ -198,6 +172,7 @@ function BarChart({ dimension, measures, orientation = "vertical", stacked = fal
198
172
  const point = payload[0].payload;
199
173
  return /* @__PURE__ */ jsx(TooltipBox, {
200
174
  title: point.label,
175
+ caption: labelOf(dimension),
201
176
  children: measures.map((measure, index) => /* @__PURE__ */ jsxs(Box, {
202
177
  sx: {
203
178
  display: "flex",
@@ -271,11 +246,11 @@ function BarChart({ dimension, measures, orientation = "vertical", stacked = fal
271
246
  barCategoryGap: "20%",
272
247
  margin: horizontal ? {
273
248
  top: 4,
274
- right: labels ? 96 : 16,
249
+ right: showLabels ? 96 : 16,
275
250
  left: 8,
276
251
  bottom: 4
277
252
  } : {
278
- top: labels ? 24 : 8,
253
+ top: showLabels ? 24 : 8,
279
254
  right: 8,
280
255
  left: 8,
281
256
  bottom: 0
@@ -288,11 +263,12 @@ function BarChart({ dimension, measures, orientation = "vertical", stacked = fal
288
263
  }),
289
264
  horizontal ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XAxis, {
290
265
  type: "number",
291
- hide: labels,
266
+ hide: showLabels,
292
267
  tick: {
293
268
  fontSize: 12,
294
269
  fill: "#555"
295
- }
270
+ },
271
+ tickFormatter: formatTick
296
272
  }), /* @__PURE__ */ jsx(YAxis, {
297
273
  type: "category",
298
274
  dataKey: "label",
@@ -314,12 +290,12 @@ function BarChart({ dimension, measures, orientation = "vertical", stacked = fal
314
290
  tickLine: false,
315
291
  axisLine: { stroke: "#ccc" }
316
292
  }), /* @__PURE__ */ jsx(YAxis, {
317
- hide: labels,
293
+ hide: showLabels,
318
294
  tick: {
319
295
  fontSize: 12,
320
296
  fill: "#555"
321
297
  },
322
- tickFormatter: (value) => formatNumber(value, 0),
298
+ tickFormatter: formatTick,
323
299
  width: 72
324
300
  })] }),
325
301
  /* @__PURE__ */ jsx(Tooltip$1, {
@@ -336,7 +312,7 @@ function BarChart({ dimension, measures, orientation = "vertical", stacked = fal
336
312
  isAnimationActive: false,
337
313
  onClick: clickToFilter ? (_bar, barIndex, event) => handleClick(barIndex, event) : void 0,
338
314
  cursor: clickToFilter ? "pointer" : void 0,
339
- children: [clickToFilter && data.map((point) => /* @__PURE__ */ jsx(Cell, { fillOpacity: filter.isDimmed(point.value) ? .35 : 1 }, String(point.value))), labels && !stacked && /* @__PURE__ */ jsx(LabelList, {
315
+ children: [clickToFilter && data.map((point) => /* @__PURE__ */ jsx(Cell, { fillOpacity: filter.isDimmed(point.value) ? .35 : 1 }, String(point.value))), showLabels && !stacked && /* @__PURE__ */ jsx(LabelList, {
340
316
  dataKey: measure,
341
317
  position: horizontal ? "right" : "top",
342
318
  formatter: (value) => formats[index]?.(value) ?? "",
@@ -365,18 +341,26 @@ const lineDomain = ([min, max]) => {
365
341
  * Barras + linha de uma dimensão, consultando o modelo do `DashboardProvider` (com os filtros dele).
366
342
  * Ex.: `<ComboChart dimension="mes" bar="custoTotal" line="custoTon" detail="volTotal" />`.
367
343
  */
368
- function ComboChart({ dimension, bar, line, detail, barLabel, lineLabel, detailLabel, formatDimension = String, formatBar, formatLine, formatDetail, hideEmpty = true, height = 360, maxLabels = 24, barColor = "#F58220", lineColor = "#555", clickToFilter = false, queryLabel }) {
344
+ 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 }) {
369
345
  const { meta, filters, setFilter } = useDashboard();
370
- const fromModel = (measure, decimals, fallback) => {
371
- const measureMeta = meta?.measures[measure];
372
- return measureMeta?.format ? (value) => formatMeasure(value, measureMeta, decimals) : fallback;
346
+ const fromModel = (measure, fallback) => {
347
+ const format = measureFormat(meta, measure, formats);
348
+ const resolved = format?.format ? format : {
349
+ ...fallback,
350
+ ...format
351
+ };
352
+ return (value) => formatMeasure(value, resolved, fallback.decimals);
373
353
  };
374
- formatBar ??= fromModel(bar, 0, (value) => formatCurrency(value, 0));
375
- formatLine ??= fromModel(line, 2, (value) => formatNumber(value, 2));
376
- formatDetail ??= detail ? fromModel(detail, 3, (value) => formatNumber(value, 3)) : (value) => formatNumber(value, 3);
377
- barLabel ??= meta?.measures[bar]?.label ?? bar;
378
- lineLabel ??= meta?.measures[line]?.label ?? line;
379
- detailLabel ??= detail && (meta?.measures[detail]?.label ?? detail);
354
+ formatBar ??= fromModel(bar, {
355
+ format: "currency",
356
+ decimals: 0
357
+ });
358
+ formatLine ??= fromModel(line, { decimals: 2 });
359
+ formatDetail ??= detail ? fromModel(detail, { decimals: 3 }) : (value) => formatNumber(value, 3);
360
+ barLabel ??= fieldLabel(meta, bar, labels);
361
+ lineLabel ??= fieldLabel(meta, line, labels);
362
+ detailLabel ??= detail && fieldLabel(meta, detail, labels);
363
+ const dimensionLabel = fieldLabel(meta, dimension, labels);
380
364
  const { rows, isLoading, error, inspectProps } = useModelQuery({
381
365
  dimensions: [dimension],
382
366
  measures: detail ? [
@@ -467,6 +451,13 @@ function ComboChart({ dimension, bar, line, detail, barLabel, lineLabel, detailL
467
451
  fontSize: 13
468
452
  },
469
453
  children: [
454
+ /* @__PURE__ */ jsx(Typography, {
455
+ sx: {
456
+ fontSize: 11,
457
+ color: "#777"
458
+ },
459
+ children: dimensionLabel
460
+ }),
470
461
  /* @__PURE__ */ jsx(Typography, {
471
462
  sx: {
472
463
  fontWeight: 700,
@@ -716,7 +707,6 @@ function DashboardCanvas({ layout, children }) {
716
707
  const EMPTY = [];
717
708
  /** Chave da coluna de percentual de uma medida (ordenação). */
718
709
  const PERCENT = "%:";
719
- const formatPercent = (value) => value === null ? "" : `${formatNumber(value * 100, 1)}%`;
720
710
  function compare(a, b) {
721
711
  if (typeof a === "number" && typeof b === "number") return a - b;
722
712
  return String(a).localeCompare(String(b), "pt-BR", { numeric: true });
@@ -725,7 +715,7 @@ function compare(a, b) {
725
715
  * Tabela de medidas por dimensões, com total e ordenação pelo cabeçalho. Ex.:
726
716
  * `<DataTable dimensions={['navio']} measures={['volTotal', 'custoTotal', 'custoTon']} />`.
727
717
  */
728
- function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDimensions = {}, showTotal = true, sortBy, sortDirection = "desc", clickToFilter = false, height = 400, queryLabel }) {
718
+ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDimensions = {}, showTotal = true, sortBy, sortDirection = "desc", clickToFilter = false, height = 400, labels, formats: formatsByMeasure, percentDecimals = 1, columnOrder, queryLabel }) {
729
719
  const { meta } = useDashboard();
730
720
  const name = queryLabel ?? `DataTable · ${dimensions.join(", ") || "total"}`;
731
721
  const firstDimension = dimensions[0];
@@ -763,9 +753,10 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
763
753
  if (y === null || y === void 0) return -1;
764
754
  return sort.direction === "asc" ? compare(x, y) : compare(y, x);
765
755
  }) : rows;
766
- const formats = Object.fromEntries(measures.map((measure) => [measure, measureFormatter(meta, measure, 2)]));
756
+ const formats = Object.fromEntries(measures.map((measure) => [measure, measureFormatter(meta, measure, 2, formatsByMeasure)]));
757
+ const formatPercent = (value) => value === null ? "" : `${formatNumber(value * 100, percentDecimals)}%`;
767
758
  const dimensionText = (dimension, value) => value === null || value === void 0 ? "(vazio)" : (formatDimensions[dimension] ?? String)(value);
768
- const labelOf = (column) => meta?.dimensions[column]?.label ?? meta?.measures[column]?.label ?? column;
759
+ const labelOf = (column) => fieldLabel(meta, column, labels);
769
760
  const headerOf = (column) => {
770
761
  if (!column.startsWith(PERCENT)) return labelOf(column);
771
762
  const measure = column.slice(2);
@@ -777,7 +768,9 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
777
768
  const total = sums[measure];
778
769
  return value === null || value === void 0 || !total ? null : Number(value) / total;
779
770
  };
780
- const valueColumns = [...measures.flatMap((measure) => percentOf.includes(measure) ? [measure, `${PERCENT}${measure}`] : [measure]), ...extraMeasures.map((measure) => `${PERCENT}${measure}`)];
771
+ const columns = [...orderedColumns([...dimensions, ...measures], columnOrder).flatMap((column) => percentOf.includes(column) && !dimensions.includes(column) ? [column, `${PERCENT}${column}`] : [column]), ...extraMeasures.map((measure) => `${PERCENT}${measure}`)];
772
+ const isDimension = (column) => dimensions.includes(column);
773
+ const totalLabelColumn = columns.find(isDimension);
781
774
  const cellText = (row, column) => column.startsWith(PERCENT) ? formatPercent(percentOfRow(row, column.slice(2))) : formats[column]?.(row[column]);
782
775
  const toggleSort = (column) => setSort((current) => current.by === column ? {
783
776
  by: column,
@@ -832,10 +825,10 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
832
825
  size: "small",
833
826
  stickyHeader: true,
834
827
  sx: { "& td, & th": { fontSize: 13 } },
835
- children: [/* @__PURE__ */ jsx(TableHead, { children: /* @__PURE__ */ jsxs(TableRow, { children: [dimensions.map((dimension) => header(dimension, false)), valueColumns.map((column) => header(column, true))] }) }), /* @__PURE__ */ jsxs(TableBody, { children: [sorted.map((row) => {
828
+ children: [/* @__PURE__ */ jsx(TableHead, { children: /* @__PURE__ */ jsx(TableRow, { children: columns.map((column) => header(column, !isDimension(column))) }) }), /* @__PURE__ */ jsxs(TableBody, { children: [sorted.map((row) => {
836
829
  const value = firstDimension ? row[firstDimension] : null;
837
830
  const clickable = clickToFilter && value !== null && value !== void 0;
838
- return /* @__PURE__ */ jsxs(TableRow, {
831
+ return /* @__PURE__ */ jsx(TableRow, {
839
832
  hover: true,
840
833
  onClick: clickable ? (event) => filter.toggle(value, event) : void 0,
841
834
  selected: clickable && filter.selected.includes(value),
@@ -843,13 +836,13 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
843
836
  cursor: clickable ? "pointer" : void 0,
844
837
  opacity: clickable && filter.isDimmed(value) ? .5 : 1
845
838
  },
846
- children: [dimensions.map((dimension) => /* @__PURE__ */ jsx(TableCell, { children: dimensionText(dimension, row[dimension]) }, dimension)), valueColumns.map((column) => /* @__PURE__ */ jsx(TableCell, {
839
+ children: columns.map((column) => isDimension(column) ? /* @__PURE__ */ jsx(TableCell, { children: dimensionText(column, row[column]) }, column) : /* @__PURE__ */ jsx(TableCell, {
847
840
  align: "right",
848
841
  sx: { whiteSpace: "nowrap" },
849
842
  children: cellText(row, column)
850
- }, column))]
843
+ }, column))
851
844
  }, dimensions.map((dimension) => String(row[dimension])).join("\0"));
852
- }), totalRow && /* @__PURE__ */ jsxs(TableRow, {
845
+ }), totalRow && /* @__PURE__ */ jsx(TableRow, {
853
846
  sx: { "& td": {
854
847
  fontWeight: 700,
855
848
  position: "sticky",
@@ -857,11 +850,11 @@ function DataTable({ dimensions = EMPTY, measures, percentOf = EMPTY, formatDime
857
850
  bgcolor: "#f3f3f3",
858
851
  borderTop: "2px solid #ddd"
859
852
  } },
860
- children: [dimensions.map((dimension, index) => /* @__PURE__ */ jsx(TableCell, { children: index === 0 ? "Total" : "" }, dimension)), valueColumns.map((column) => /* @__PURE__ */ jsx(TableCell, {
853
+ children: columns.map((column) => isDimension(column) ? /* @__PURE__ */ jsx(TableCell, { children: column === totalLabelColumn ? "Total" : "" }, column) : /* @__PURE__ */ jsx(TableCell, {
861
854
  align: "right",
862
855
  sx: { whiteSpace: "nowrap" },
863
856
  children: column.startsWith(PERCENT) ? formatPercent(sums[column.slice(2)] ? 1 : null) : formats[column]?.(totalRow[column])
864
- }, column))]
857
+ }, column))
865
858
  })] })]
866
859
  })
867
860
  })]
@@ -874,7 +867,7 @@ const OTHERS = "Outros";
874
867
  * Rosca de uma medida por uma dimensão, com o total no centro e a legenda ao lado (nome, % e valor).
875
868
  * Ex.: `<DonutChart dimension="operacao_status" measure="custoTotal" />`.
876
869
  */
877
- function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = String, clickToFilter = false, height = 300, queryLabel }) {
870
+ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = String, clickToFilter = false, height = 300, labels, formats, percentDecimals = 1, queryLabel }) {
878
871
  const { meta } = useDashboard();
879
872
  const { rows, isLoading, error, inspectProps } = useModelQuery({
880
873
  dimensions: [dimension],
@@ -883,7 +876,7 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
883
876
  label: queryLabel ?? `DonutChart · ${dimension}`
884
877
  });
885
878
  const filter = useClickFilter(dimension, clickToFilter);
886
- const format = measureFormatter(meta, measure, 0);
879
+ const format = measureFormatter(meta, measure, 0, formats);
887
880
  const positive = rows.map((row) => ({
888
881
  value: row[dimension] ?? "",
889
882
  amount: numberOf(row, measure) ?? 0
@@ -906,7 +899,7 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
906
899
  color: OTHER_COLOR
907
900
  });
908
901
  const total = slices.reduce((sum, slice) => sum + slice.amount, 0);
909
- const percent = (amount) => `${formatNumber(amount / total * 100, 1)}%`;
902
+ const percent = (amount) => `${formatNumber(amount / total * 100, percentDecimals)}%`;
910
903
  const placeholder = chartPlaceholder({
911
904
  height,
912
905
  error,
@@ -922,7 +915,10 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
922
915
  const slice = payload[0].payload;
923
916
  return /* @__PURE__ */ jsxs(TooltipBox, {
924
917
  title: slice.label,
918
+ caption: fieldLabel(meta, dimension, labels),
925
919
  children: [
920
+ fieldLabel(meta, measure, labels),
921
+ ": ",
926
922
  format(slice.amount),
927
923
  " · ",
928
924
  percent(slice.amount)
@@ -1067,7 +1063,7 @@ function DonutChart({ dimension, measure, maxSlices = 6, formatDimension = Strin
1067
1063
  * dimensões: razões (custo/t) saem do total, não de uma média. Ex.:
1068
1064
  * `<KpiCard label="Custo total" measure="custoTotal" secondary="custoTon" />`.
1069
1065
  */
1070
- function KpiCard({ measure, label, secondary, secondaryLabel, queryLabel }) {
1066
+ function KpiCard({ measure, label, secondary, secondaryLabel, labels, formats, queryLabel }) {
1071
1067
  const { meta } = useDashboard();
1072
1068
  const { rows, isLoading, error, inspectProps } = useModelQuery({
1073
1069
  measures: secondary ? [measure, secondary] : [measure],
@@ -1076,6 +1072,7 @@ function KpiCard({ measure, label, secondary, secondaryLabel, queryLabel }) {
1076
1072
  const row = rows[0];
1077
1073
  const value = row ? numberOf(row, measure) : null;
1078
1074
  const extra = row && secondary ? numberOf(row, secondary) : null;
1075
+ label ??= labels?.[measure];
1079
1076
  return /* @__PURE__ */ jsxs(Box, {
1080
1077
  ...inspectProps,
1081
1078
  sx: {
@@ -1112,7 +1109,7 @@ function KpiCard({ measure, label, secondary, secondaryLabel, queryLabel }) {
1112
1109
  opacity: isLoading ? .5 : 1,
1113
1110
  transition: "opacity .2s"
1114
1111
  },
1115
- children: error ? "Erro" : value === null ? "—" : measureFormatter(meta, measure, 0)(value)
1112
+ children: error ? "Erro" : value === null ? "—" : measureFormatter(meta, measure, 0, formats)(value)
1116
1113
  }),
1117
1114
  secondary && /* @__PURE__ */ jsxs(Box, {
1118
1115
  sx: {
@@ -1122,7 +1119,7 @@ function KpiCard({ measure, label, secondary, secondaryLabel, queryLabel }) {
1122
1119
  minHeight: 20
1123
1120
  },
1124
1121
  children: [
1125
- secondaryLabel ?? meta?.measures[secondary]?.label ?? secondary,
1122
+ secondaryLabel ?? fieldLabel(meta, secondary, labels),
1126
1123
  ":",
1127
1124
  " ",
1128
1125
  /* @__PURE__ */ jsx(Box, {
@@ -1131,7 +1128,7 @@ function KpiCard({ measure, label, secondary, secondaryLabel, queryLabel }) {
1131
1128
  fontWeight: 600,
1132
1129
  color: "#444"
1133
1130
  },
1134
- children: extra === null ? "—" : measureFormatter(meta, secondary, 2)(extra)
1131
+ children: extra === null ? "—" : measureFormatter(meta, secondary, 2, formats)(extra)
1135
1132
  })
1136
1133
  ]
1137
1134
  })
@@ -1364,6 +1361,6 @@ function DashboardFilters({ layout, formatters = {}, gap = 2 }) {
1364
1361
  });
1365
1362
  }
1366
1363
  //#endregion
1367
- export { BarChart, ComboChart, DashboardCanvas, DashboardCharts, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartPlaceholder, formatCurrency, formatMeasure, formatNumber, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
1364
+ export { BarChart, ComboChart, DashboardCanvas, DashboardCharts, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartPlaceholder, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
1368
1365
 
1369
1366
  //# sourceMappingURL=index.js.map