@bluemarble/bi 0.5.0 → 0.6.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,6 +1,7 @@
1
- import { A as measureHome, C as AGGREGATION_LABEL, E as isAggregate, M as parseColumnRef, O as validateModel, P as referencedTables, S as AGGREGATIONS, _ as validateLayout, a as FILTER_FIELDS, c as chartOfType, d as filterReferences, h as orderedColumns, i as CARD_FIELDS, j as modelEdges, l as chartReferences, m as layoutModelWarnings, n as CANVAS_PRESETS, p as layoutFileContent, r as CARDS_FIELDS, s as chartColumns, t as BUILTIN_CHART_TYPES, v as canonicalJson, w as VALUE_FORMATS, x as toModelCode } from "./layout-QuOYFmp1.js";
1
+ import { a as toModelCode, c as VALUE_FORMATS, f as validateModel, g as parseColumnRef, h as modelEdges, m as measureHome, o as AGGREGATIONS, s as AGGREGATION_LABEL, t as canonicalJson, u as isAggregate, v as referencedTables } from "./codegen-DkfI6UJr.js";
2
2
  import { n as diffRows } from "./compare-DloZhg1T.js";
3
- import { d as useDashboard, f as errorMessage, h as parseLayoutItemKey, m as layoutItemKey, n as INSPECT_ATTRIBUTE, o as formatMeasure, p as LAYOUT_ITEM_ATTRIBUTE, t as ModelDictionary } from "./ModelDictionary-D3JzCd6x.js";
3
+ import { C as RULE_OPERATOR_LABEL, S as RULE_OPERATORS, _ as tableColumns, a as FILTER_FIELDS, b as COLUMN_VERTICAL_ALIGNS, c as chartOfType, d as filterReferences, h as percentColumn, i as CARD_FIELDS, l as chartReferences, m as layoutModelWarnings, n as CANVAS_PRESETS, p as layoutFileContent, r as CARDS_FIELDS, s as chartColumns, t as BUILTIN_CHART_TYPES, v as validateLayout, w as UNARY_OPERATORS, x as NUMERIC_OPERATORS, y as COLUMN_ALIGNS } from "./layout-DAFeHIuP.js";
4
+ import { d as useDashboard, f as errorMessage, h as parseLayoutItemKey, m as layoutItemKey, n as INSPECT_ATTRIBUTE, o as formatMeasure, p as LAYOUT_ITEM_ATTRIBUTE, t as ModelDictionary } from "./ModelDictionary-B26oLtPH.js";
4
5
  import { Alert, Autocomplete, Box, Button, Checkbox, Chip, CircularProgress, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControlLabel, IconButton, List, ListItemButton, ListSubheader, Menu, MenuItem, MenuList, Paper, Popover, Popper, Portal, Tab, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TableSortLabel, Tabs, TextField, ThemeProvider, ToggleButton, ToggleButtonGroup, Tooltip, Typography, createTheme } from "@mui/material";
5
6
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1761,23 +1762,84 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
1761
1762
  label: measure.label
1762
1763
  }));
1763
1764
  const entries = Object.entries(fields);
1765
+ const columnLabel = (name) => name.startsWith("%:") ? `% ${fieldLabelOf(model, name.slice(2))}` : fieldLabelOf(model, name);
1766
+ const columnOptionsOf = (names) => names.map((name) => ({
1767
+ name,
1768
+ label: columnLabel(name)
1769
+ }));
1770
+ const fieldOptions = [
1771
+ ...columnOptionsOf(columns).map((option) => ({
1772
+ ...option,
1773
+ group: "Colunas do gráfico"
1774
+ })),
1775
+ ...dimensions.filter((option) => !columns.includes(option.name)).map((option) => ({
1776
+ ...option,
1777
+ group: "Dimensões fora do gráfico"
1778
+ })),
1779
+ ...measures.filter((option) => !columns.includes(option.name)).map((option) => ({
1780
+ ...option,
1781
+ group: "Medidas fora do gráfico"
1782
+ }))
1783
+ ];
1764
1784
  const keyOf = (kind) => entries.find(([, spec]) => spec.kind === kind)?.[0];
1765
1785
  const labelsKey = keyOf("labels");
1766
1786
  const formatsKey = keyOf("formats");
1767
- const columnOptions = !!(labelsKey || formatsKey);
1787
+ const alignsKey = keyOf("aligns");
1788
+ const verticalKey = keyOf("verticalAligns");
1789
+ const columnOptions = !!(labelsKey || formatsKey || alignsKey || verticalKey);
1768
1790
  const formattersKey = columnOptions ? keyOf("formatters") : void 0;
1769
1791
  const single = (kind) => entries.filter(([, spec]) => spec.kind === kind);
1770
1792
  const formatterKey = columnOptions && single("dimension").length === 1 && single("formatter").length === 1 ? single("formatter")[0]?.[0] : void 0;
1771
1793
  const labels = labelsKey && values[labelsKey] || {};
1772
1794
  const formats = formatsKey && values[formatsKey] || {};
1795
+ const aligns = alignsKey && values[alignsKey] || {};
1796
+ const verticals = verticalKey && values[verticalKey] || {};
1797
+ const alignProps = (column, defaultAlign) => ({
1798
+ align: alignsKey ? aligns[column] ?? "" : void 0,
1799
+ defaultAlign,
1800
+ onAlign: (next) => alignsKey && set(alignsKey, pruned(withField(aligns, column, next), columns)),
1801
+ verticalAlign: verticalKey ? verticals[column] ?? "" : void 0,
1802
+ onVerticalAlign: (next) => verticalKey && set(verticalKey, pruned(withField(verticals, column, next), columns))
1803
+ });
1804
+ const resetAligns = (column, changes) => {
1805
+ if (alignsKey) changes[alignsKey] = pruned(withField(aligns, column, void 0), columns);
1806
+ if (verticalKey) changes[verticalKey] = pruned(withField(verticals, column, void 0), columns);
1807
+ };
1808
+ const setChanges = (changes) => {
1809
+ if (onSetMany) onSetMany(changes);
1810
+ else for (const [key, value] of Object.entries(changes)) set(key, value);
1811
+ };
1773
1812
  const dimensionFormatters = formattersKey && values[formattersKey] || {};
1774
1813
  const optionsButton = (kind, name) => {
1775
1814
  if (!columnOptions || !name) return null;
1815
+ if (kind === "percent") {
1816
+ if (!alignsKey && !verticalKey) return null;
1817
+ const column = percentColumn(name);
1818
+ return /* @__PURE__ */ jsx(ColumnOptions, {
1819
+ kind,
1820
+ name,
1821
+ model,
1822
+ label: void 0,
1823
+ onLabel: () => {},
1824
+ format: void 0,
1825
+ onFormat: () => {},
1826
+ formatter: void 0,
1827
+ formatters,
1828
+ onFormatter: () => {},
1829
+ ...alignProps(column, "right"),
1830
+ onReset: () => {
1831
+ const changes = {};
1832
+ resetAligns(column, changes);
1833
+ setChanges(changes);
1834
+ }
1835
+ });
1836
+ }
1776
1837
  const canFormatter = kind === "dimension" && !!(formattersKey || formatterKey);
1777
1838
  return /* @__PURE__ */ jsx(ColumnOptions, {
1778
1839
  kind,
1779
1840
  name,
1780
1841
  model,
1842
+ ...alignProps(name, kind === "dimension" ? "left" : "right"),
1781
1843
  label: labelsKey ? labels[name] ?? "" : void 0,
1782
1844
  onLabel: (text) => labelsKey && set(labelsKey, pruned(withField(labels, name, text), columns)),
1783
1845
  format: kind === "measure" && formatsKey ? formats[name] ?? {} : void 0,
@@ -1797,6 +1859,7 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
1797
1859
  onReset: () => {
1798
1860
  const changes = {};
1799
1861
  if (labelsKey) changes[labelsKey] = pruned(withField(labels, name, void 0), columns);
1862
+ resetAligns(name, changes);
1800
1863
  if (formatsKey && kind === "measure") changes[formatsKey] = pruned(withField(formats, name, void 0), chosenMeasures);
1801
1864
  if (canFormatter && formattersKey) changes[formattersKey] = pruned(withField(dimensionFormatters, name, void 0), chosenDimensions);
1802
1865
  else if (canFormatter && formatterKey) changes[formatterKey] = void 0;
@@ -1808,14 +1871,17 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
1808
1871
  const merged = (() => {
1809
1872
  for (const [dimensionsKey, spec] of entries) {
1810
1873
  if (spec.kind !== "dimensions") continue;
1811
- const pair = entries.find(([, other]) => other.kind === "measures" && other.section === spec.section);
1874
+ const pair = entries.find(([, other]) => other.kind === "measures" && !other.percent && other.section === spec.section);
1812
1875
  if (!pair) continue;
1813
1876
  const order = entries.find(([, other]) => other.kind === "columnOrder")?.[0];
1877
+ const percents = order ? entries.find(([, other]) => other.kind === "measures" && other.percent && other.section === spec.section) : void 0;
1814
1878
  return {
1815
1879
  dimensions: dimensionsKey,
1816
1880
  dimensionsSpec: spec,
1817
1881
  measures: pair[0],
1818
1882
  measuresSpec: pair[1],
1883
+ percents: percents?.[0],
1884
+ percentsSpec: percents?.[1],
1819
1885
  order
1820
1886
  };
1821
1887
  }
@@ -1823,18 +1889,26 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
1823
1889
  const mergedInput = (pair) => {
1824
1890
  const dimensionNames = values[pair.dimensions] ?? [];
1825
1891
  const measureNames = values[pair.measures] ?? [];
1826
- const all = [...dimensionNames.map((name) => ({
1827
- kind: "dimension",
1828
- name
1829
- })), ...measureNames.map((name) => ({
1830
- kind: "measure",
1831
- name
1832
- }))];
1892
+ const percentNames = pair.percents ? values[pair.percents] ?? [] : [];
1893
+ const all = [
1894
+ ...dimensionNames.map((name) => ({
1895
+ kind: "dimension",
1896
+ name
1897
+ })),
1898
+ ...measureNames.map((name) => ({
1899
+ kind: "measure",
1900
+ name
1901
+ })),
1902
+ ...percentNames.map((name) => ({
1903
+ kind: "percent",
1904
+ name
1905
+ }))
1906
+ ];
1833
1907
  const order = pair.order ? values[pair.order] : void 0;
1834
- const sortedNames = orderedColumns(all.map((item) => item.name), order);
1908
+ const sortedColumns = tableColumns([...dimensionNames, ...measureNames], percentNames, order);
1835
1909
  const pool = [...all];
1836
- const items = sortedNames.map((name) => {
1837
- const index = pool.findIndex((item) => item.name === name);
1910
+ const items = sortedColumns.map((column) => {
1911
+ const index = pool.findIndex((item) => itemColumn(item) === column);
1838
1912
  return pool.splice(index, 1)[0];
1839
1913
  });
1840
1914
  const required = !!pair.dimensionsSpec.required || !!pair.measuresSpec.required;
@@ -1842,19 +1916,22 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
1842
1916
  items,
1843
1917
  options: {
1844
1918
  dimension: options(pair.dimensionsSpec),
1845
- measure: options(pair.measuresSpec)
1919
+ measure: options(pair.measuresSpec),
1920
+ ...pair.percentsSpec && { percent: options(pair.percentsSpec) }
1846
1921
  },
1847
1922
  extra: (item) => optionsButton(item.kind, item.name),
1848
1923
  error: !!pair.measuresSpec.required && !measureNames.length || !!pair.dimensionsSpec.required && !dimensionNames.length || required && !items.length,
1849
1924
  onChange: (next) => {
1850
1925
  const dimensionList = next.filter((item) => item.kind === "dimension");
1851
1926
  const measureList = next.filter((item) => item.kind === "measure");
1852
- const names = next.map((item) => item.name);
1853
- const standard = [...dimensionList, ...measureList].map((item) => item.name);
1927
+ const percentList = next.filter((item) => item.kind === "percent").map((i) => i.name);
1928
+ const names = next.map(itemColumn);
1929
+ const standard = tableColumns([...dimensionList, ...measureList].map((item) => item.name), percentList);
1854
1930
  const changes = {
1855
1931
  [pair.dimensions]: dimensionList.length || pair.dimensionsSpec.required ? dimensionList.map((item) => item.name) : void 0,
1856
1932
  [pair.measures]: measureList.length || pair.measuresSpec.required ? measureList.map((item) => item.name) : void 0
1857
1933
  };
1934
+ if (pair.percents) changes[pair.percents] = percentList.length ? percentList : void 0;
1858
1935
  if (pair.order) changes[pair.order] = names.join("\0") === standard.join("\0") ? void 0 : names;
1859
1936
  if (onSetMany) onSetMany(changes);
1860
1937
  else for (const [key, value] of Object.entries(changes)) set(key, value);
@@ -1868,10 +1945,7 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
1868
1945
  case "measure":
1869
1946
  case "measures": return measures;
1870
1947
  case "formatter": return formatters.map((name) => ({ name }));
1871
- case "column": return columns.map((name) => ({
1872
- name,
1873
- label: model.dimensions[name]?.label ?? model.measures[name]?.label
1874
- }));
1948
+ case "column": return spec.outside ? fieldOptions : columnOptionsOf(columns);
1875
1949
  case "enum": return spec.values.map((name) => ({
1876
1950
  name,
1877
1951
  label: spec.labels?.[name]
@@ -1958,6 +2032,13 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
1958
2032
  }
1959
2033
  });
1960
2034
  }
2035
+ case "formatRules": return /* @__PURE__ */ jsx(FormatRulesInput, {
2036
+ rules: value ?? [],
2037
+ fieldOptions,
2038
+ columnOptions: columnOptionsOf(columns),
2039
+ model,
2040
+ onChange: (rules) => set(key, rules.length ? rules : void 0)
2041
+ });
1961
2042
  case "formatters": {
1962
2043
  const map = value ?? {};
1963
2044
  if (!chosenDimensions.length) return /* @__PURE__ */ jsx(Typography, {
@@ -2019,14 +2100,17 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
2019
2100
  };
2020
2101
  const hidden = new Set([
2021
2102
  merged?.measures,
2103
+ merged?.percents,
2022
2104
  ...entries.filter(([, spec]) => spec.kind === "columnOrder").map(([key]) => key),
2023
2105
  labelsKey,
2024
2106
  formatsKey,
2107
+ alignsKey,
2108
+ verticalKey,
2025
2109
  formattersKey,
2026
2110
  formatterKey
2027
2111
  ].filter(Boolean));
2028
2112
  const sections = [...new Set(entries.filter(([key]) => !hidden.has(key)).map(([, spec]) => spec.section))];
2029
- const fullRow = (spec) => spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "formatters";
2113
+ const fullRow = (spec) => spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "formatters" || spec.kind === "formatRules";
2030
2114
  const gridColumn = (spec, key) => fullRow(spec) ? "1 / -1" : key === "title" && formColumns > 1 ? "span 2" : void 0;
2031
2115
  return /* @__PURE__ */ jsx(Fragment, { children: sections.map((section) => {
2032
2116
  const inSection = entries.filter(([, spec]) => spec.section === section);
@@ -2040,12 +2124,351 @@ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters,
2040
2124
  gridColumn: gridColumn(spec, key),
2041
2125
  alignSelf: spec.kind === "boolean" ? "end" : void 0
2042
2126
  },
2043
- children: spec.kind === "boolean" ? withHint(input(key, spec), spec.hint) : merged?.dimensions === key ? field("Dimensões e medidas", input(key, spec), `${merged.order ? "Em qualquer ordem: é a ordem das colunas. " : ""}${[spec.hint, merged.measuresSpec.hint].filter(Boolean).join(" · ")}`) : field(spec.label, input(key, spec), spec.hint)
2127
+ children: spec.kind === "boolean" ? withHint(input(key, spec), spec.hint) : merged?.dimensions === key ? field(merged.percents ? "Dimensões, medidas e %" : "Dimensões e medidas", input(key, spec), `${merged.order ? "Em qualquer ordem: é a ordem das colunas. " : ""}${[
2128
+ spec.hint,
2129
+ merged.measuresSpec.hint,
2130
+ merged.percentsSpec?.hint && `% do total: ${merged.percentsSpec.hint}`
2131
+ ].filter(Boolean).join(" · ")}`) : field(spec.label, input(key, spec), spec.hint)
2044
2132
  }, key))]
2045
2133
  })
2046
2134
  }, section);
2047
2135
  }) });
2048
2136
  }
2137
+ const fieldLabelOf = (model, name) => model.dimensions[name]?.label ?? model.measures[name]?.label ?? name;
2138
+ /** Exemplo de célula com o estilo de uma regra. */
2139
+ const RULE_SAMPLE = "Exemplo";
2140
+ const DEFAULT_RULE_BACKGROUND = "#ffe3e3";
2141
+ /**
2142
+ * Regras de formatação condicional, uma por cartão: "se <campo> <operador> <valor>, pinta <a célula
2143
+ * de uma coluna | a linha toda> com <texto, fundo, negrito>". A ordem importa (a de baixo ganha):
2144
+ * reordena com ↑/↓.
2145
+ */
2146
+ function FormatRulesInput({ rules, fieldOptions, columnOptions, model, onChange }) {
2147
+ const update = (index, change) => onChange(rules.map((rule, i) => i === index ? change(rule) : rule));
2148
+ const columns = columnOptions.map((option) => option.name);
2149
+ const parseValue = (rule, text) => {
2150
+ const trimmed = text.trim();
2151
+ if (!trimmed) return void 0;
2152
+ const number = Number(trimmed.replace(",", "."));
2153
+ if (NUMERIC_OPERATORS.includes(rule.operator)) return Number.isFinite(number) ? number : void 0;
2154
+ if (rule.operator === "contains") return trimmed;
2155
+ return !(model.dimensions[rule.field]?.type === "string") && Number.isFinite(number) ? number : trimmed;
2156
+ };
2157
+ const withOperator = (rule, operator) => {
2158
+ const { value, to, ...rest } = rule;
2159
+ if (UNARY_OPERATORS.includes(operator)) return {
2160
+ ...rest,
2161
+ operator
2162
+ };
2163
+ const numeric = NUMERIC_OPERATORS.includes(operator);
2164
+ const next = {
2165
+ ...rest,
2166
+ operator,
2167
+ value: numeric ? Number.isFinite(Number(value)) ? Number(value) : 0 : value ?? ""
2168
+ };
2169
+ if (operator === "between") next.to = to ?? next.value;
2170
+ return next;
2171
+ };
2172
+ return /* @__PURE__ */ jsxs(Box, {
2173
+ sx: {
2174
+ display: "flex",
2175
+ flexDirection: "column",
2176
+ gap: .75,
2177
+ maxWidth: 760
2178
+ },
2179
+ children: [rules.map((rule, index) => {
2180
+ const painted = rule.target === "row" ? void 0 : rule.column ?? rule.field;
2181
+ const lost = painted !== void 0 && !columns.includes(painted);
2182
+ return /* @__PURE__ */ jsxs(Box, {
2183
+ sx: {
2184
+ border: 1,
2185
+ borderColor: "divider",
2186
+ borderRadius: 1,
2187
+ p: .75,
2188
+ display: "flex",
2189
+ flexDirection: "column",
2190
+ gap: .5,
2191
+ "&:hover .row-actions": { visibility: "visible" }
2192
+ },
2193
+ children: [
2194
+ /* @__PURE__ */ jsxs(Box, {
2195
+ sx: {
2196
+ display: "flex",
2197
+ gap: .5,
2198
+ alignItems: "center",
2199
+ flexWrap: "wrap"
2200
+ },
2201
+ children: [
2202
+ /* @__PURE__ */ jsxs(Typography, {
2203
+ sx: {
2204
+ fontSize: 12,
2205
+ color: "text.secondary"
2206
+ },
2207
+ children: [index + 1, ". Se"]
2208
+ }),
2209
+ /* @__PURE__ */ jsx(Box, {
2210
+ sx: { flex: "2 1 140px" },
2211
+ children: /* @__PURE__ */ jsx(NameSelect, {
2212
+ value: rule.field,
2213
+ options: fieldOptions,
2214
+ required: true,
2215
+ onChange: (field) => update(index, (current) => ({
2216
+ ...current,
2217
+ field
2218
+ }))
2219
+ })
2220
+ }),
2221
+ /* @__PURE__ */ jsx(TextField, {
2222
+ select: true,
2223
+ size: "small",
2224
+ value: rule.operator,
2225
+ onChange: (e) => update(index, (current) => withOperator(current, e.target.value)),
2226
+ sx: {
2227
+ ...inputSx$1,
2228
+ flex: "1 1 110px"
2229
+ },
2230
+ children: RULE_OPERATORS.map((operator) => /* @__PURE__ */ jsx(MenuItem, {
2231
+ value: operator,
2232
+ children: RULE_OPERATOR_LABEL[operator]
2233
+ }, operator))
2234
+ }),
2235
+ !UNARY_OPERATORS.includes(rule.operator) && /* @__PURE__ */ jsx(Box, {
2236
+ sx: { flex: "1 1 80px" },
2237
+ children: /* @__PURE__ */ jsx(CommitText, {
2238
+ value: rule.value === void 0 ? "" : String(rule.value),
2239
+ placeholder: "valor",
2240
+ error: rule.value === void 0,
2241
+ onCommit: (text) => update(index, (current) => ({
2242
+ ...current,
2243
+ value: parseValue(current, text)
2244
+ }))
2245
+ })
2246
+ }),
2247
+ rule.operator === "between" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Typography, {
2248
+ sx: {
2249
+ fontSize: 12,
2250
+ color: "text.secondary"
2251
+ },
2252
+ children: "e"
2253
+ }), /* @__PURE__ */ jsx(Box, {
2254
+ sx: { flex: "1 1 80px" },
2255
+ children: /* @__PURE__ */ jsx(CommitText, {
2256
+ value: rule.to === void 0 ? "" : String(rule.to),
2257
+ placeholder: "até",
2258
+ error: rule.to === void 0,
2259
+ onCommit: (text) => update(index, (current) => {
2260
+ const to = parseValue(current, text);
2261
+ return {
2262
+ ...current,
2263
+ to: typeof to === "number" ? to : void 0
2264
+ };
2265
+ })
2266
+ })
2267
+ })] }),
2268
+ /* @__PURE__ */ jsxs(Box, {
2269
+ className: "row-actions",
2270
+ sx: {
2271
+ display: "flex",
2272
+ visibility: "hidden"
2273
+ },
2274
+ children: [
2275
+ /* @__PURE__ */ jsx(SmallButton, {
2276
+ title: "Subir",
2277
+ disabled: index === 0,
2278
+ onClick: () => onChange(moveTo(rules, index, index - 1)),
2279
+ children: "↑"
2280
+ }),
2281
+ /* @__PURE__ */ jsx(SmallButton, {
2282
+ title: "Descer",
2283
+ disabled: index === rules.length - 1,
2284
+ onClick: () => onChange(moveTo(rules, index, index + 2)),
2285
+ children: "↓"
2286
+ }),
2287
+ /* @__PURE__ */ jsx(SmallButton, {
2288
+ title: "Remover",
2289
+ onClick: () => onChange(rules.filter((_, i) => i !== index)),
2290
+ children: "×"
2291
+ })
2292
+ ]
2293
+ })
2294
+ ]
2295
+ }),
2296
+ /* @__PURE__ */ jsxs(Box, {
2297
+ sx: {
2298
+ display: "flex",
2299
+ gap: .5,
2300
+ alignItems: "center",
2301
+ flexWrap: "wrap"
2302
+ },
2303
+ children: [
2304
+ /* @__PURE__ */ jsx(Typography, {
2305
+ sx: {
2306
+ fontSize: 12,
2307
+ color: "text.secondary"
2308
+ },
2309
+ children: "pinta"
2310
+ }),
2311
+ /* @__PURE__ */ jsxs(TextField, {
2312
+ select: true,
2313
+ size: "small",
2314
+ value: rule.target === "row" ? "row" : "cell",
2315
+ onChange: (e) => update(index, ({ target: _target, column: _column, ...rest }) => e.target.value === "row" ? {
2316
+ ...rest,
2317
+ target: "row"
2318
+ } : rest),
2319
+ sx: {
2320
+ ...inputSx$1,
2321
+ flex: "1 1 110px"
2322
+ },
2323
+ children: [/* @__PURE__ */ jsx(MenuItem, {
2324
+ value: "cell",
2325
+ children: "a célula de"
2326
+ }), /* @__PURE__ */ jsx(MenuItem, {
2327
+ value: "row",
2328
+ children: "a linha toda"
2329
+ })]
2330
+ }),
2331
+ rule.target !== "row" && /* @__PURE__ */ jsx(Box, {
2332
+ sx: { flex: "2 1 140px" },
2333
+ children: /* @__PURE__ */ jsx(NameSelect, {
2334
+ value: rule.column ?? "",
2335
+ options: columnOptions,
2336
+ emptyLabel: `o próprio campo${columns.includes(rule.field) ? "" : " (fora do gráfico)"}`,
2337
+ onChange: (column) => update(index, (current) => withField(current, "column", column || void 0))
2338
+ })
2339
+ }),
2340
+ /* @__PURE__ */ jsx(Box, {
2341
+ sx: {
2342
+ px: 1,
2343
+ py: .25,
2344
+ fontSize: 12,
2345
+ border: 1,
2346
+ borderColor: "divider",
2347
+ color: rule.color,
2348
+ bgcolor: rule.background,
2349
+ fontWeight: rule.bold ? 700 : void 0
2350
+ },
2351
+ children: RULE_SAMPLE
2352
+ })
2353
+ ]
2354
+ }),
2355
+ lost && /* @__PURE__ */ jsxs(Typography, {
2356
+ sx: {
2357
+ fontSize: 11,
2358
+ color: "warning.main"
2359
+ },
2360
+ children: [
2361
+ "\"",
2362
+ painted,
2363
+ "\" não é coluna do gráfico: escolha a coluna a pintar ou a linha toda"
2364
+ ]
2365
+ }),
2366
+ /* @__PURE__ */ jsxs(Box, {
2367
+ sx: {
2368
+ display: "grid",
2369
+ gap: .5,
2370
+ gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))",
2371
+ alignItems: "end"
2372
+ },
2373
+ children: [
2374
+ field("Cor do texto", /* @__PURE__ */ jsx(ColorInput, {
2375
+ value: rule.color,
2376
+ fallback: "#000000",
2377
+ onCommit: (color) => update(index, (current) => withField(current, "color", color))
2378
+ })),
2379
+ field("Fundo", /* @__PURE__ */ jsx(ColorInput, {
2380
+ value: rule.background,
2381
+ fallback: "#ffffff",
2382
+ onCommit: (color) => update(index, (current) => withField(current, "background", color))
2383
+ })),
2384
+ field("Alinhamento", /* @__PURE__ */ jsxs(TextField, {
2385
+ select: true,
2386
+ size: "small",
2387
+ fullWidth: true,
2388
+ value: rule.align ?? "",
2389
+ onChange: (e) => update(index, (current) => withField(current, "align", e.target.value || void 0)),
2390
+ slotProps: { select: { displayEmpty: true } },
2391
+ sx: inputSx$1,
2392
+ children: [/* @__PURE__ */ jsx(MenuItem, {
2393
+ value: "",
2394
+ children: /* @__PURE__ */ jsx("em", { children: "não muda" })
2395
+ }), COLUMN_ALIGNS.map((option) => /* @__PURE__ */ jsx(MenuItem, {
2396
+ value: option,
2397
+ children: ALIGN_LABEL[option]
2398
+ }, option))]
2399
+ })),
2400
+ field("Vertical", /* @__PURE__ */ jsxs(TextField, {
2401
+ select: true,
2402
+ size: "small",
2403
+ fullWidth: true,
2404
+ value: rule.verticalAlign ?? "",
2405
+ onChange: (e) => update(index, (current) => withField(current, "verticalAlign", e.target.value || void 0)),
2406
+ slotProps: { select: { displayEmpty: true } },
2407
+ sx: inputSx$1,
2408
+ children: [/* @__PURE__ */ jsx(MenuItem, {
2409
+ value: "",
2410
+ children: /* @__PURE__ */ jsx("em", { children: "não muda" })
2411
+ }), COLUMN_VERTICAL_ALIGNS.map((option) => /* @__PURE__ */ jsx(MenuItem, {
2412
+ value: option,
2413
+ children: VERTICAL_ALIGN_LABEL[option]
2414
+ }, option))]
2415
+ })),
2416
+ /* @__PURE__ */ jsx(FormControlLabel, {
2417
+ control: /* @__PURE__ */ jsx(Checkbox, {
2418
+ size: "small",
2419
+ checked: !!rule.bold,
2420
+ onChange: (e) => update(index, (current) => withField(current, "bold", e.target.checked || void 0))
2421
+ }),
2422
+ label: /* @__PURE__ */ jsx(Typography, {
2423
+ sx: { fontSize: 12 },
2424
+ children: "Negrito"
2425
+ })
2426
+ })
2427
+ ]
2428
+ })
2429
+ ]
2430
+ }, index);
2431
+ }), /* @__PURE__ */ jsx(Button, {
2432
+ size: "small",
2433
+ variant: "outlined",
2434
+ disabled: !fieldOptions.length,
2435
+ onClick: () => onChange([...rules, {
2436
+ field: columns.find((name) => !name.startsWith("%:")) ?? "",
2437
+ operator: ">",
2438
+ value: 0,
2439
+ background: DEFAULT_RULE_BACKGROUND
2440
+ }]),
2441
+ sx: {
2442
+ alignSelf: "flex-start",
2443
+ textTransform: "none"
2444
+ },
2445
+ children: "+ regra"
2446
+ })]
2447
+ });
2448
+ }
2449
+ /** Campo de texto que só manda o valor no Enter ou ao sair (número parcial não vira valor). */
2450
+ function CommitText({ value, placeholder, error, onCommit }) {
2451
+ const [typing, setTyping] = useState(null);
2452
+ const commit = () => {
2453
+ if (typing === null) return;
2454
+ setTyping(null);
2455
+ if (typing !== value) onCommit(typing);
2456
+ };
2457
+ return /* @__PURE__ */ jsx(TextField, {
2458
+ size: "small",
2459
+ fullWidth: true,
2460
+ value: typing ?? value,
2461
+ placeholder,
2462
+ error: error && typing === null,
2463
+ onChange: (e) => setTyping(e.target.value),
2464
+ onBlur: commit,
2465
+ onKeyDown: (e) => {
2466
+ if (e.key === "Enter") commit();
2467
+ if (e.key === "Escape") setTyping(null);
2468
+ },
2469
+ sx: inputSx$1
2470
+ });
2471
+ }
2049
2472
  /**
2050
2473
  * Cor com o seletor nativo. Mexer no seletor só muda a amostra: o valor vai para o editor quando
2051
2474
  * ele fecha — o `change` nativo; o `onChange` do React dispara a cada movimento. O texto entra no
@@ -2115,6 +2538,16 @@ function pruned(map, keep) {
2115
2538
  const next = Object.fromEntries(Object.entries(map).filter(([key]) => keep.includes(key)));
2116
2539
  return Object.keys(next).length ? next : void 0;
2117
2540
  }
2541
+ const ALIGN_LABEL = {
2542
+ left: "à esquerda",
2543
+ center: "centralizado",
2544
+ right: "à direita"
2545
+ };
2546
+ const VERTICAL_ALIGN_LABEL = {
2547
+ top: "em cima",
2548
+ middle: "no meio",
2549
+ bottom: "embaixo"
2550
+ };
2118
2551
  const FORMAT_LABEL = {
2119
2552
  number: "número",
2120
2553
  integer: "inteiro",
@@ -2128,11 +2561,11 @@ const SAMPLE = 1234567.891;
2128
2561
  * (dimensão) e formato (medida). Vazio = o do dicionário do modelo (que aparece como placeholder).
2129
2562
  * Opção ausente (`undefined`) = o tipo do gráfico não tem esse campo.
2130
2563
  */
2131
- function ColumnOptions({ kind, name, model, label, onLabel, format, onFormat, formatter, formatters, onFormatter, onReset }) {
2564
+ function ColumnOptions({ kind, name, model, label, onLabel, format, onFormat, formatter, formatters, onFormatter, align, defaultAlign, onAlign, verticalAlign, onVerticalAlign, onReset }) {
2132
2565
  const [anchor, setAnchor] = useState(null);
2133
2566
  const fromModel = kind === "measure" ? model.measures[name] : model.dimensions[name];
2134
2567
  const measureMeta = kind === "measure" ? model.measures[name] : void 0;
2135
- const customized = !!label || !!formatter || format !== void 0 && Object.keys(format).length > 0;
2568
+ const customized = !!label || !!formatter || !!align || !!verticalAlign || format !== void 0 && Object.keys(format).length > 0;
2136
2569
  const setFormat = (key, next) => onFormat(withField(format ?? {}, key, next));
2137
2570
  const decimals = format?.decimals;
2138
2571
  const resolved = {
@@ -2223,6 +2656,46 @@ function ColumnOptions({ kind, name, model, label, onLabel, format, onFormat, fo
2223
2656
  onChange: (e) => onLabel(e.target.value),
2224
2657
  sx: inputSx$1
2225
2658
  }), "Legenda, tooltip e cabeçalho, só neste gráfico; vazio = rótulo do dicionário"),
2659
+ align !== void 0 && field("Alinhamento", /* @__PURE__ */ jsxs(TextField, {
2660
+ select: true,
2661
+ size: "small",
2662
+ fullWidth: true,
2663
+ value: align,
2664
+ onChange: (e) => onAlign(e.target.value || void 0),
2665
+ slotProps: { select: { displayEmpty: true } },
2666
+ sx: small,
2667
+ children: [/* @__PURE__ */ jsx(MenuItem, {
2668
+ value: "",
2669
+ children: /* @__PURE__ */ jsxs("em", { children: [
2670
+ "padrão (",
2671
+ ALIGN_LABEL[defaultAlign],
2672
+ ")"
2673
+ ] })
2674
+ }), COLUMN_ALIGNS.map((option) => /* @__PURE__ */ jsx(MenuItem, {
2675
+ value: option,
2676
+ children: ALIGN_LABEL[option]
2677
+ }, option))]
2678
+ }), "Horizontal: cabeçalho, células e total da coluna"),
2679
+ verticalAlign !== void 0 && field("Alinhamento vertical", /* @__PURE__ */ jsxs(TextField, {
2680
+ select: true,
2681
+ size: "small",
2682
+ fullWidth: true,
2683
+ value: verticalAlign,
2684
+ onChange: (e) => onVerticalAlign(e.target.value || void 0),
2685
+ slotProps: { select: { displayEmpty: true } },
2686
+ sx: small,
2687
+ children: [/* @__PURE__ */ jsx(MenuItem, {
2688
+ value: "",
2689
+ children: /* @__PURE__ */ jsxs("em", { children: [
2690
+ "padrão (",
2691
+ VERTICAL_ALIGN_LABEL.middle,
2692
+ ")"
2693
+ ] })
2694
+ }), COLUMN_VERTICAL_ALIGNS.map((option) => /* @__PURE__ */ jsx(MenuItem, {
2695
+ value: option,
2696
+ children: VERTICAL_ALIGN_LABEL[option]
2697
+ }, option))]
2698
+ }), "Aparece quando a linha tem texto em mais de uma linha"),
2226
2699
  formatter !== void 0 && field("Formatter", /* @__PURE__ */ jsx(NameSelect, {
2227
2700
  value: formatter,
2228
2701
  options: formatters.map((option) => ({ name: option })),
@@ -2400,13 +2873,14 @@ function NameSelect({ value, options, required, emptyLabel, onChange }) {
2400
2873
  value,
2401
2874
  children: [value, " (não existe)"]
2402
2875
  }),
2403
- options.map((option) => /* @__PURE__ */ jsx(MenuItem, {
2876
+ options.flatMap((option, index) => [...option.group && option.group !== options[index - 1]?.group ? [/* @__PURE__ */ jsx(ListSubheader, { children: option.group }, `h:${option.group}`)] : [], /* @__PURE__ */ jsx(MenuItem, {
2404
2877
  value: option.name,
2405
2878
  children: /* @__PURE__ */ jsx(OptionText, { option })
2406
- }, option.name))
2879
+ }, option.name)])
2407
2880
  ]
2408
2881
  });
2409
2882
  }
2883
+ const itemColumn = (item) => item.kind === "percent" ? percentColumn(item.name) : item.name;
2410
2884
  const KIND_STYLE = {
2411
2885
  dimension: {
2412
2886
  icon: "◇",
@@ -2419,6 +2893,12 @@ const KIND_STYLE = {
2419
2893
  color: "#F58220",
2420
2894
  noun: "medida",
2421
2895
  group: "Medidas"
2896
+ },
2897
+ percent: {
2898
+ icon: "%",
2899
+ color: "#9775fa",
2900
+ noun: "% do total",
2901
+ group: "% do total (da medida)"
2422
2902
  }
2423
2903
  };
2424
2904
  /**
@@ -7631,4 +8111,4 @@ function ToggleButton$1({ draft, loading, problems, inspecting, onOpen }) {
7631
8111
  //#endregion
7632
8112
  export { DashboardDevtools as default };
7633
8113
 
7634
- //# sourceMappingURL=DashboardDevtools-6v5HDe-A.js.map
8114
+ //# sourceMappingURL=DashboardDevtools-xhkgdAkr.js.map