@bluemarble/bi 0.4.0 → 0.6.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,7 +1,8 @@
1
- import { A as modelEdges, C as VALUE_FORMATS, D as validateModel, N as referencedTables, S as AGGREGATION_LABEL, T as isAggregate, _ as canonicalJson, a as FILTER_FIELDS, b as toModelCode, c as chartOfType, d as filterReferences, g as validateLayout, i as CARD_FIELDS, j as parseColumnRef, k as measureHome, 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, x as AGGREGATIONS } from "./layout-676sEvvb.js";
1
+ import { _ as referencedTables, a as AGGREGATIONS, d as validateModel, h as parseColumnRef, i as toModelCode, l as isAggregate, m as modelEdges, o as AGGREGATION_LABEL, p as measureHome, s as VALUE_FORMATS, t as canonicalJson } from "./codegen-DBe4-XW_.js";
2
2
  import { n as diffRows } from "./compare-DloZhg1T.js";
3
- import { a as useDashboard, c as layoutItemKey, l as parseLayoutItemKey, n as INSPECT_ATTRIBUTE, o as errorMessage, s as LAYOUT_ITEM_ATTRIBUTE, t as ModelDictionary } from "./ModelDictionary-Y7W9oK1J.js";
4
- import { Alert, Autocomplete, Box, Button, Checkbox, Chip, CircularProgress, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControlLabel, IconButton, List, ListItemButton, Menu, MenuItem, MenuList, Paper, Popper, Portal, Tab, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TableSortLabel, Tabs, TextField, ThemeProvider, ToggleButton, ToggleButtonGroup, Tooltip, Typography, createTheme } from "@mui/material";
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-BS51SQcJ.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-C2tzlOY8.js";
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";
7
8
  import { createPortal } from "react-dom";
@@ -960,8 +961,8 @@ const chartSummary = (chart, types) => {
960
961
  };
961
962
  /**
962
963
  * Editor dos layouts da página (filtros do `<DashboardFilters>` e gráficos do `<DashboardCharts>`).
963
- * Cada edição válida vira rascunho na hora — a página re-renderiza com ele — e "Salvar no arquivo"
964
- * grava o JSON do layout.
964
+ * Como no modelo: as edições ficam no editor até "Aplicar na página" (vira rascunho e a página
965
+ * re-renderiza com ele); "Salvar no arquivo" grava o JSON do layout com o que está no editor.
965
966
  */
966
967
  function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
967
968
  const { layouts } = devtools;
@@ -970,7 +971,8 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
970
971
  const [layoutId, setLayoutId] = useState(null);
971
972
  const entry = entries.find((item) => item.registered.id === layoutId) ?? entries[0] ?? null;
972
973
  const reference = entry ? entry.draft ?? entry.registered : null;
973
- const [editing, setEditing] = useState(reference);
974
+ const pendingOrReference = () => (entry && layouts.pendingOf(entry.registered.id)) ?? reference;
975
+ const [editing, setEditingState] = useState(pendingOrReference);
974
976
  const [selection, setSelection] = useState(null);
975
977
  const [sidebar, setSidebar] = useState(loadSidebar);
976
978
  const [drag, setDrag] = useState(null);
@@ -981,7 +983,19 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
981
983
  const [save, setSave] = useState(null);
982
984
  const [addMenu, setAddMenu] = useState(null);
983
985
  const referenceKey = canonicalJson(reference);
984
- useEffect(() => setEditing(reference), [referenceKey]);
986
+ /** Muda o editor e guarda como pendente o que difere do que a página mostra. */
987
+ const setEditing = (next) => {
988
+ setEditingState(next);
989
+ if (entry) layouts.setPending(entry.registered.id, next && canonicalJson(next) !== referenceKey ? next : null);
990
+ };
991
+ const firstRun = useRef(true);
992
+ useEffect(() => {
993
+ if (firstRun.current) {
994
+ firstRun.current = false;
995
+ return;
996
+ }
997
+ setEditingState(pendingOrReference());
998
+ }, [referenceKey]);
985
999
  useEffect(() => {
986
1000
  const parsed = picked && parseLayoutItemKey(picked.item);
987
1001
  if (!parsed) return;
@@ -1025,13 +1039,16 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
1025
1039
  const selected = selection?.kind === "filter" || selection?.kind === "canvas" || selection?.kind === "cards" ? null : charts.find((chart) => chart.id === selection?.key) ?? charts[0] ?? null;
1026
1040
  const layoutKey = entry.registered.id;
1027
1041
  const differsFromFile = canonicalJson(editing) !== canonicalJson(entry.registered);
1042
+ const dirty = canonicalJson(editing) !== referenceKey;
1028
1043
  const update = (next) => {
1029
1044
  setEditing(next);
1030
1045
  setSave(null);
1031
- if (!validateLayout(next, chartTypes).length) {
1032
- devtools.store.expectRefetch();
1033
- layouts.setDraft(entry.registered.id, next);
1034
- }
1046
+ };
1047
+ const apply = () => {
1048
+ if (validateLayout(editing, chartTypes).length) return;
1049
+ devtools.store.expectRefetch();
1050
+ layouts.setPending(entry.registered.id, null);
1051
+ layouts.setDraft(entry.registered.id, editing);
1035
1052
  };
1036
1053
  const setCharts = (next) => update({
1037
1054
  ...editing,
@@ -1134,6 +1151,11 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
1134
1151
  } }
1135
1152
  });
1136
1153
  setSave(mode === "write" ? null : result.devtools?.save ?? null);
1154
+ if (mode === "write" && editing && entry) {
1155
+ devtools.store.expectRefetch();
1156
+ layouts.setPending(entry.registered.id, null);
1157
+ layouts.setDraft(entry.registered.id, editing);
1158
+ }
1137
1159
  } catch (error) {
1138
1160
  setActionError(errorMessage(error));
1139
1161
  } finally {
@@ -1398,29 +1420,53 @@ function LayoutPanel({ devtools, model, capabilities, picked, onHover }) {
1398
1420
  borderColor: "divider"
1399
1421
  },
1400
1422
  children: [
1423
+ dirty && /* @__PURE__ */ jsx(Typography, {
1424
+ sx: {
1425
+ fontSize: 12,
1426
+ color: "warning.main",
1427
+ mr: "auto"
1428
+ },
1429
+ children: "Edições não aplicadas"
1430
+ }),
1401
1431
  /* @__PURE__ */ jsx(CopyButton, {
1402
1432
  text: layoutFileContent(editing),
1403
1433
  label: "Copiar JSON"
1404
1434
  }),
1435
+ /* @__PURE__ */ jsx(Button, {
1436
+ size: "small",
1437
+ disabled: !dirty,
1438
+ onClick: () => setEditing(reference),
1439
+ title: "Desfaz as edições ainda não aplicadas",
1440
+ children: "Desfazer"
1441
+ }),
1405
1442
  /* @__PURE__ */ jsx(Button, {
1406
1443
  size: "small",
1407
1444
  color: "warning",
1408
- disabled: !entry.draft && !differsFromFile,
1445
+ disabled: !entry.draft,
1409
1446
  onClick: () => {
1410
1447
  devtools.store.expectRefetch();
1448
+ layouts.setPending(entry.registered.id, null);
1411
1449
  layouts.setDraft(entry.registered.id, null);
1412
- setEditing(entry.registered);
1450
+ setEditingState(entry.registered);
1413
1451
  },
1414
- title: "Volta ao layout do arquivo",
1452
+ title: "Volta a página e o editor ao layout do arquivo",
1415
1453
  children: "Descartar rascunho"
1416
1454
  }),
1417
1455
  capabilities?.saveLayout && /* @__PURE__ */ jsx(Button, {
1418
1456
  size: "small",
1419
- variant: "contained",
1457
+ variant: "outlined",
1420
1458
  disabled: !differsFromFile || !!errors.length || busy,
1421
1459
  onClick: () => saveFile("preview"),
1422
- title: `Reescreve ${entry.registered.id}.json`,
1460
+ title: `Reescreve ${entry.registered.id}.json com o que está no editor`,
1423
1461
  children: busy ? "Gerando…" : "Salvar no arquivo"
1462
+ }),
1463
+ /* @__PURE__ */ jsx(Button, {
1464
+ size: "small",
1465
+ variant: "contained",
1466
+ disabled: !dirty || !!errors.length,
1467
+ onClick: apply,
1468
+ title: "Mostra as edições na página (sem gravar o arquivo)",
1469
+ children: "Aplicar na página"
1424
1470
  })
1425
1471
  ]
1426
1472
  }),
@@ -1646,11 +1692,13 @@ function SmallButton({ title, disabled, onClick, children }) {
1646
1692
  }
1647
1693
  function ChartForm({ chart, cards, types, model, formatters, columns, onChange }) {
1648
1694
  const set = (key, value) => onChange((current) => withField(current, key, value));
1695
+ const setMany = (changes) => onChange((current) => Object.entries(changes).reduce((chart, [key, value]) => withField(chart, key, value), current));
1649
1696
  const setCard = (key, value) => onChange((current) => withField(current, "card", nonEmpty(withField(current.card ?? {}, key, value))));
1650
1697
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FieldsForm, {
1651
1698
  fields: types[chart.type]?.fields ?? {},
1652
1699
  values: chart,
1653
1700
  onSet: set,
1701
+ onSetMany: setMany,
1654
1702
  model,
1655
1703
  formatters,
1656
1704
  columns: chartColumns(chart, types),
@@ -1704,7 +1752,7 @@ function FilterForm({ filter, model, formatters, columns, onChange }) {
1704
1752
  });
1705
1753
  }
1706
1754
  /** Formulário gerado dos `FieldSpec` (campos do tipo de gráfico ou do filtro). */
1707
- function FieldsForm({ fields, values, onSet: set, model, formatters, columns, formColumns, chosenDimensions, chosenMeasures = [], header }) {
1755
+ function FieldsForm({ fields, values, onSet: set, onSetMany, model, formatters, columns, formColumns, chosenDimensions, chosenMeasures = [], header }) {
1708
1756
  const dimensions = Object.entries(model.dimensions).map(([name, dim]) => ({
1709
1757
  name,
1710
1758
  label: dim.label
@@ -1714,6 +1762,182 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1714
1762
  label: measure.label
1715
1763
  }));
1716
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
+ ];
1784
+ const keyOf = (kind) => entries.find(([, spec]) => spec.kind === kind)?.[0];
1785
+ const labelsKey = keyOf("labels");
1786
+ const formatsKey = keyOf("formats");
1787
+ const alignsKey = keyOf("aligns");
1788
+ const verticalKey = keyOf("verticalAligns");
1789
+ const columnOptions = !!(labelsKey || formatsKey || alignsKey || verticalKey);
1790
+ const formattersKey = columnOptions ? keyOf("formatters") : void 0;
1791
+ const single = (kind) => entries.filter(([, spec]) => spec.kind === kind);
1792
+ const formatterKey = columnOptions && single("dimension").length === 1 && single("formatter").length === 1 ? single("formatter")[0]?.[0] : void 0;
1793
+ const labels = labelsKey && values[labelsKey] || {};
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
+ };
1812
+ const dimensionFormatters = formattersKey && values[formattersKey] || {};
1813
+ const optionsButton = (kind, name) => {
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
+ }
1837
+ const canFormatter = kind === "dimension" && !!(formattersKey || formatterKey);
1838
+ return /* @__PURE__ */ jsx(ColumnOptions, {
1839
+ kind,
1840
+ name,
1841
+ model,
1842
+ ...alignProps(name, kind === "dimension" ? "left" : "right"),
1843
+ label: labelsKey ? labels[name] ?? "" : void 0,
1844
+ onLabel: (text) => labelsKey && set(labelsKey, pruned(withField(labels, name, text), columns)),
1845
+ format: kind === "measure" && formatsKey ? formats[name] ?? {} : void 0,
1846
+ onFormat: (format) => {
1847
+ if (!formatsKey) return;
1848
+ const map = { ...formats };
1849
+ if (Object.keys(format).length) map[name] = format;
1850
+ else delete map[name];
1851
+ set(formatsKey, pruned(map, chosenMeasures));
1852
+ },
1853
+ formatter: !canFormatter ? void 0 : formattersKey ? dimensionFormatters[name] ?? "" : (formatterKey && values[formatterKey]) ?? "",
1854
+ formatters,
1855
+ onFormatter: (formatter) => {
1856
+ if (formattersKey) set(formattersKey, pruned(withField(dimensionFormatters, name, formatter), chosenDimensions));
1857
+ else if (formatterKey) set(formatterKey, formatter || void 0);
1858
+ },
1859
+ onReset: () => {
1860
+ const changes = {};
1861
+ if (labelsKey) changes[labelsKey] = pruned(withField(labels, name, void 0), columns);
1862
+ resetAligns(name, changes);
1863
+ if (formatsKey && kind === "measure") changes[formatsKey] = pruned(withField(formats, name, void 0), chosenMeasures);
1864
+ if (canFormatter && formattersKey) changes[formattersKey] = pruned(withField(dimensionFormatters, name, void 0), chosenDimensions);
1865
+ else if (canFormatter && formatterKey) changes[formatterKey] = void 0;
1866
+ if (onSetMany) onSetMany(changes);
1867
+ else for (const [key, value] of Object.entries(changes)) set(key, value);
1868
+ }
1869
+ });
1870
+ };
1871
+ const merged = (() => {
1872
+ for (const [dimensionsKey, spec] of entries) {
1873
+ if (spec.kind !== "dimensions") continue;
1874
+ const pair = entries.find(([, other]) => other.kind === "measures" && !other.percent && other.section === spec.section);
1875
+ if (!pair) continue;
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;
1878
+ return {
1879
+ dimensions: dimensionsKey,
1880
+ dimensionsSpec: spec,
1881
+ measures: pair[0],
1882
+ measuresSpec: pair[1],
1883
+ percents: percents?.[0],
1884
+ percentsSpec: percents?.[1],
1885
+ order
1886
+ };
1887
+ }
1888
+ })();
1889
+ const mergedInput = (pair) => {
1890
+ const dimensionNames = values[pair.dimensions] ?? [];
1891
+ const measureNames = values[pair.measures] ?? [];
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
+ ];
1907
+ const order = pair.order ? values[pair.order] : void 0;
1908
+ const sortedColumns = tableColumns([...dimensionNames, ...measureNames], percentNames, order);
1909
+ const pool = [...all];
1910
+ const items = sortedColumns.map((column) => {
1911
+ const index = pool.findIndex((item) => itemColumn(item) === column);
1912
+ return pool.splice(index, 1)[0];
1913
+ });
1914
+ const required = !!pair.dimensionsSpec.required || !!pair.measuresSpec.required;
1915
+ return /* @__PURE__ */ jsx(ColumnList, {
1916
+ items,
1917
+ options: {
1918
+ dimension: options(pair.dimensionsSpec),
1919
+ measure: options(pair.measuresSpec),
1920
+ ...pair.percentsSpec && { percent: options(pair.percentsSpec) }
1921
+ },
1922
+ extra: (item) => optionsButton(item.kind, item.name),
1923
+ error: !!pair.measuresSpec.required && !measureNames.length || !!pair.dimensionsSpec.required && !dimensionNames.length || required && !items.length,
1924
+ onChange: (next) => {
1925
+ const dimensionList = next.filter((item) => item.kind === "dimension");
1926
+ const measureList = next.filter((item) => item.kind === "measure");
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);
1930
+ const changes = {
1931
+ [pair.dimensions]: dimensionList.length || pair.dimensionsSpec.required ? dimensionList.map((item) => item.name) : void 0,
1932
+ [pair.measures]: measureList.length || pair.measuresSpec.required ? measureList.map((item) => item.name) : void 0
1933
+ };
1934
+ if (pair.percents) changes[pair.percents] = percentList.length ? percentList : void 0;
1935
+ if (pair.order) changes[pair.order] = names.join("\0") === standard.join("\0") ? void 0 : names;
1936
+ if (onSetMany) onSetMany(changes);
1937
+ else for (const [key, value] of Object.entries(changes)) set(key, value);
1938
+ }
1939
+ });
1940
+ };
1717
1941
  const options = (spec) => {
1718
1942
  switch (spec.kind) {
1719
1943
  case "dimension":
@@ -1721,10 +1945,7 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1721
1945
  case "measure":
1722
1946
  case "measures": return measures;
1723
1947
  case "formatter": return formatters.map((name) => ({ name }));
1724
- case "column": return columns.map((name) => ({
1725
- name,
1726
- label: model.dimensions[name]?.label ?? model.measures[name]?.label
1727
- }));
1948
+ case "column": return spec.outside ? fieldOptions : columnOptionsOf(columns);
1728
1949
  case "enum": return spec.values.map((name) => ({
1729
1950
  name,
1730
1951
  label: spec.labels?.[name]
@@ -1793,11 +2014,30 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1793
2014
  sx: inputSx$1
1794
2015
  });
1795
2016
  case "dimensions":
1796
- case "measures": return /* @__PURE__ */ jsx(MultiNameSelect, {
1797
- value: value ?? [],
1798
- options: options(spec),
1799
- required: !!spec.required,
1800
- onChange: (next) => set(key, next.length || spec.required ? next : void 0)
2017
+ case "measures": {
2018
+ const kind = spec.kind === "dimensions" ? "dimension" : "measure";
2019
+ if (merged?.dimensions === key) return mergedInput(merged);
2020
+ const names = value ?? [];
2021
+ return /* @__PURE__ */ jsx(ColumnList, {
2022
+ items: names.map((name) => ({
2023
+ kind,
2024
+ name
2025
+ })),
2026
+ options: { [kind]: options(spec) },
2027
+ extra: (item) => optionsButton(item.kind, item.name),
2028
+ error: !!spec.required && !names.length,
2029
+ onChange: (next) => {
2030
+ const list = next.map((item) => item.name);
2031
+ set(key, list.length || spec.required ? list : void 0);
2032
+ }
2033
+ });
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)
1801
2041
  });
1802
2042
  case "formatters": {
1803
2043
  const map = value ?? {};
@@ -1838,45 +2078,40 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1838
2078
  }, dimension))
1839
2079
  });
1840
2080
  }
1841
- case "labels":
1842
- if (!columns.length) return /* @__PURE__ */ jsx(Typography, {
1843
- sx: {
1844
- fontSize: 12,
1845
- color: "text.disabled"
1846
- },
1847
- children: "sem colunas"
1848
- });
1849
- return /* @__PURE__ */ jsx(LabelsEditor, {
1850
- value: value ?? {},
1851
- columns,
1852
- model,
1853
- onChange: (next) => set(key, next)
2081
+ default: {
2082
+ const select = /* @__PURE__ */ jsx(NameSelect, {
2083
+ value: value ?? "",
2084
+ options: options(spec),
2085
+ required: spec.required,
2086
+ emptyLabel: spec.kind === "enum" && spec.default ? `padrão (${spec.labels?.[spec.default] ?? spec.default})` : void 0,
2087
+ onChange: (name) => set(key, name === spec.default ? void 0 : name)
1854
2088
  });
1855
- case "formats":
1856
- if (!chosenMeasures.length) return /* @__PURE__ */ jsx(Typography, {
2089
+ const button = spec.kind === "dimension" || spec.kind === "measure" ? optionsButton(spec.kind, value) : null;
2090
+ return button ? /* @__PURE__ */ jsxs(Box, {
1857
2091
  sx: {
1858
- fontSize: 12,
1859
- color: "text.disabled"
2092
+ display: "flex",
2093
+ gap: .25,
2094
+ alignItems: "center"
1860
2095
  },
1861
- children: "sem medidas"
1862
- });
1863
- return /* @__PURE__ */ jsx(FormatsEditor, {
1864
- value: value ?? {},
1865
- measures: chosenMeasures,
1866
- model,
1867
- onChange: (next) => set(key, next)
1868
- });
1869
- default: return /* @__PURE__ */ jsx(NameSelect, {
1870
- value: value ?? "",
1871
- options: options(spec),
1872
- required: spec.required,
1873
- emptyLabel: spec.kind === "enum" && spec.default ? `padrão (${spec.labels?.[spec.default] ?? spec.default})` : void 0,
1874
- onChange: (name) => set(key, name === spec.default ? void 0 : name)
1875
- });
2096
+ children: [select, button]
2097
+ }) : select;
2098
+ }
1876
2099
  }
1877
2100
  };
1878
- const sections = [...new Set(entries.map(([, spec]) => spec.section))];
1879
- const wide = (spec, key) => key === "title" || spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "formatters" || spec.kind === "labels" || spec.kind === "formats";
2101
+ const hidden = new Set([
2102
+ merged?.measures,
2103
+ merged?.percents,
2104
+ ...entries.filter(([, spec]) => spec.kind === "columnOrder").map(([key]) => key),
2105
+ labelsKey,
2106
+ formatsKey,
2107
+ alignsKey,
2108
+ verticalKey,
2109
+ formattersKey,
2110
+ formatterKey
2111
+ ].filter(Boolean));
2112
+ const sections = [...new Set(entries.filter(([key]) => !hidden.has(key)).map(([, spec]) => spec.section))];
2113
+ const fullRow = (spec) => spec.kind === "dimensions" || spec.kind === "measures" || spec.kind === "formatters" || spec.kind === "formatRules";
2114
+ const gridColumn = (spec, key) => fullRow(spec) ? "1 / -1" : key === "title" && formColumns > 1 ? "span 2" : void 0;
1880
2115
  return /* @__PURE__ */ jsx(Fragment, { children: sections.map((section) => {
1881
2116
  const inSection = entries.filter(([, spec]) => spec.section === section);
1882
2117
  return /* @__PURE__ */ jsx(Section, {
@@ -1884,20 +2119,359 @@ function FieldsForm({ fields, values, onSet: set, model, formatters, columns, fo
1884
2119
  collapseKey: `layout:form:${section}`,
1885
2120
  children: /* @__PURE__ */ jsxs(Box, {
1886
2121
  sx: formGrid(formColumns),
1887
- children: [section === sections[0] && header, inSection.map(([key, spec]) => /* @__PURE__ */ jsx(Box, {
2122
+ children: [section === sections[0] && header, inSection.filter(([key]) => !hidden.has(key)).map(([key, spec]) => /* @__PURE__ */ jsx(Box, {
1888
2123
  sx: {
1889
- gridColumn: wide(spec, key) && formColumns > 1 ? "span 2" : void 0,
2124
+ gridColumn: gridColumn(spec, key),
1890
2125
  alignSelf: spec.kind === "boolean" ? "end" : void 0
1891
2126
  },
1892
- children: spec.kind === "boolean" ? withHint(input(key, spec), spec.hint) : 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)
1893
2132
  }, key))]
1894
2133
  })
1895
2134
  }, section);
1896
2135
  }) });
1897
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";
1898
2141
  /**
1899
- * Cor com o seletor nativo. Mexer no seletor só muda a amostra: o rascunho (e a página) muda quando
1900
- * ele fecha — o `change` nativo; o `onChange` do React dispara a cada movimento. O texto aplica no
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
+ }
2472
+ /**
2473
+ * Cor com o seletor nativo. Mexer no seletor só muda a amostra: o valor vai para o editor quando
2474
+ * ele fecha — o `change` nativo; o `onChange` do React dispara a cada movimento. O texto entra no
1901
2475
  * Enter ou ao sair do campo.
1902
2476
  */
1903
2477
  function ColorInput({ value, fallback, onCommit }) {
@@ -1964,61 +2538,40 @@ function pruned(map, keep) {
1964
2538
  const next = Object.fromEntries(Object.entries(map).filter(([key]) => keep.includes(key)));
1965
2539
  return Object.keys(next).length ? next : void 0;
1966
2540
  }
1967
- const columnNameSx = {
1968
- fontFamily: MONO,
1969
- fontSize: 12,
1970
- minWidth: 90,
1971
- maxWidth: 140,
1972
- overflow: "hidden",
1973
- textOverflow: "ellipsis",
1974
- whiteSpace: "nowrap"
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"
1975
2550
  };
1976
- /** Rótulo de cada coluna do gráfico; o placeholder é o rótulo do dicionário. */
1977
- function LabelsEditor({ value, columns, model, onChange }) {
1978
- return /* @__PURE__ */ jsx(Box, {
1979
- sx: {
1980
- display: "flex",
1981
- flexDirection: "column",
1982
- gap: .5
1983
- },
1984
- children: columns.map((column) => /* @__PURE__ */ jsxs(Box, {
1985
- sx: {
1986
- display: "flex",
1987
- gap: .5,
1988
- alignItems: "center"
1989
- },
1990
- children: [/* @__PURE__ */ jsx(Tooltip, {
1991
- title: model.dimensions[column] ? "dimensão" : "medida",
1992
- placement: "left",
1993
- children: /* @__PURE__ */ jsx(Typography, {
1994
- sx: columnNameSx,
1995
- children: column
1996
- })
1997
- }), /* @__PURE__ */ jsx(TextField, {
1998
- size: "small",
1999
- fullWidth: true,
2000
- value: value[column] ?? "",
2001
- placeholder: model.dimensions[column]?.label ?? model.measures[column]?.label ?? column,
2002
- onChange: (e) => onChange(pruned(withField(value, column, e.target.value), columns)),
2003
- sx: inputSx$1
2004
- })]
2005
- }, column))
2006
- });
2007
- }
2008
2551
  const FORMAT_LABEL = {
2009
2552
  number: "número",
2010
2553
  integer: "inteiro",
2011
2554
  currency: "R$",
2012
2555
  percent: "%"
2013
2556
  };
2014
- /** Formato de cada medida do gráfico; vazio = o do dicionário (que aparece como placeholder). */
2015
- function FormatsEditor({ value, measures, model, onChange }) {
2016
- const setFormat = (measure, key, next) => {
2017
- const format = withField(value[measure] ?? {}, key, next);
2018
- const map = { ...value };
2019
- if (Object.keys(format).length) map[measure] = format;
2020
- else delete map[measure];
2021
- onChange(pruned(map, measures));
2557
+ /** Número de exemplo da prévia do formato. */
2558
+ const SAMPLE = 1234567.891;
2559
+ /**
2560
+ * Ícone à direita de uma dimensão/medida que abre as opções dela no gráfico: rótulo, formatter
2561
+ * (dimensão) e formato (medida). Vazio = o do dicionário do modelo (que aparece como placeholder).
2562
+ * Opção ausente (`undefined`) = o tipo do gráfico não tem esse campo.
2563
+ */
2564
+ function ColumnOptions({ kind, name, model, label, onLabel, format, onFormat, formatter, formatters, onFormatter, align, defaultAlign, onAlign, verticalAlign, onVerticalAlign, onReset }) {
2565
+ const [anchor, setAnchor] = useState(null);
2566
+ const fromModel = kind === "measure" ? model.measures[name] : model.dimensions[name];
2567
+ const measureMeta = kind === "measure" ? model.measures[name] : void 0;
2568
+ const customized = !!label || !!formatter || !!align || !!verticalAlign || format !== void 0 && Object.keys(format).length > 0;
2569
+ const setFormat = (key, next) => onFormat(withField(format ?? {}, key, next));
2570
+ const decimals = format?.decimals;
2571
+ const resolved = {
2572
+ ...measureMeta?.format !== void 0 && { format: measureMeta.format },
2573
+ ...measureMeta?.decimals !== void 0 && { decimals: measureMeta.decimals },
2574
+ ...format
2022
2575
  };
2023
2576
  const small = {
2024
2577
  ...inputSx$1,
@@ -2028,104 +2581,232 @@ function FormatsEditor({ value, measures, model, onChange }) {
2028
2581
  px: 1
2029
2582
  }
2030
2583
  };
2031
- return /* @__PURE__ */ jsx(Box, {
2032
- sx: {
2033
- display: "flex",
2034
- flexDirection: "column",
2035
- gap: .75
2584
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Tooltip, {
2585
+ title: customized ? "Opções da coluna (personalizada)" : "Opções da coluna",
2586
+ children: /* @__PURE__ */ jsx(IconButton, {
2587
+ size: "small",
2588
+ "aria-label": `Opções de ${name}`,
2589
+ onClick: (e) => setAnchor(e.currentTarget),
2590
+ sx: {
2591
+ p: .25,
2592
+ fontSize: 14,
2593
+ color: customized ? "primary.main" : "text.secondary"
2594
+ },
2595
+ children: "⚙"
2596
+ })
2597
+ }), /* @__PURE__ */ jsxs(Popover, {
2598
+ open: !!anchor,
2599
+ anchorEl: anchor,
2600
+ onClose: () => setAnchor(null),
2601
+ anchorOrigin: {
2602
+ vertical: "bottom",
2603
+ horizontal: "right"
2036
2604
  },
2037
- children: measures.map((measure) => {
2038
- const format = value[measure] ?? {};
2039
- const fromModel = model.measures[measure];
2040
- const decimals = format.decimals;
2041
- return /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsxs(Typography, {
2042
- sx: {
2043
- ...columnNameSx,
2044
- maxWidth: "none",
2045
- mb: .25
2046
- },
2047
- children: [measure, fromModel?.label && /* @__PURE__ */ jsx(Box, {
2605
+ transformOrigin: {
2606
+ vertical: "top",
2607
+ horizontal: "right"
2608
+ },
2609
+ slotProps: { paper: { sx: {
2610
+ p: 1.5,
2611
+ width: 320
2612
+ } } },
2613
+ children: [/* @__PURE__ */ jsxs(Typography, {
2614
+ sx: {
2615
+ fontSize: 12,
2616
+ fontWeight: 700,
2617
+ mb: 1
2618
+ },
2619
+ children: [
2620
+ /* @__PURE__ */ jsx(Box, {
2621
+ component: "span",
2622
+ sx: {
2623
+ color: KIND_STYLE[kind].color,
2624
+ mr: .75
2625
+ },
2626
+ children: KIND_STYLE[kind].icon
2627
+ }),
2628
+ /* @__PURE__ */ jsx(Box, {
2629
+ component: "span",
2630
+ sx: { fontFamily: MONO },
2631
+ children: name
2632
+ }),
2633
+ /* @__PURE__ */ jsx(Box, {
2048
2634
  component: "span",
2049
2635
  sx: {
2050
2636
  color: "text.secondary",
2051
- ml: 1,
2052
- fontFamily: "inherit"
2637
+ fontWeight: 400,
2638
+ ml: 1
2053
2639
  },
2054
- children: fromModel.label
2055
- })]
2056
- }), /* @__PURE__ */ jsxs(Box, {
2057
- sx: {
2058
- display: "grid",
2059
- gap: .5,
2060
- alignItems: "center",
2061
- gridTemplateColumns: "minmax(88px, 1.4fr) minmax(52px, .8fr) 1fr 1fr auto"
2062
- },
2063
- children: [
2064
- /* @__PURE__ */ jsxs(TextField, {
2065
- select: true,
2066
- size: "small",
2067
- value: format.format ?? "",
2068
- onChange: (e) => setFormat(measure, "format", e.target.value || void 0),
2069
- slotProps: { select: { displayEmpty: true } },
2070
- sx: small,
2071
- children: [/* @__PURE__ */ jsx(MenuItem, {
2072
- value: "",
2073
- children: /* @__PURE__ */ jsxs("em", { children: ["padrão", fromModel?.format ? ` (${FORMAT_LABEL[fromModel.format]})` : ""] })
2074
- }), VALUE_FORMATS.map((name) => /* @__PURE__ */ jsx(MenuItem, {
2075
- value: name,
2076
- children: FORMAT_LABEL[name]
2077
- }, name))]
2640
+ children: KIND_STYLE[kind].noun
2641
+ })
2642
+ ]
2643
+ }), /* @__PURE__ */ jsxs(Box, {
2644
+ sx: {
2645
+ display: "flex",
2646
+ flexDirection: "column",
2647
+ gap: 1.25
2648
+ },
2649
+ children: [
2650
+ label !== void 0 && field("Rótulo", /* @__PURE__ */ jsx(TextField, {
2651
+ size: "small",
2652
+ fullWidth: true,
2653
+ autoFocus: true,
2654
+ value: label,
2655
+ placeholder: fromModel?.label ?? name,
2656
+ onChange: (e) => onLabel(e.target.value),
2657
+ sx: inputSx$1
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"),
2699
+ formatter !== void 0 && field("Formatter", /* @__PURE__ */ jsx(NameSelect, {
2700
+ value: formatter,
2701
+ options: formatters.map((option) => ({ name: option })),
2702
+ onChange: onFormatter
2703
+ }), "Formatters passados ao <DashboardCharts formatters={{ ... }}> da página"),
2704
+ format !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
2705
+ /* @__PURE__ */ jsxs(Box, {
2706
+ sx: {
2707
+ display: "grid",
2708
+ gridTemplateColumns: "1fr 1fr",
2709
+ gap: 1
2710
+ },
2711
+ children: [
2712
+ field("Formato", /* @__PURE__ */ jsxs(TextField, {
2713
+ select: true,
2714
+ size: "small",
2715
+ fullWidth: true,
2716
+ value: format.format ?? "",
2717
+ onChange: (e) => setFormat("format", e.target.value || void 0),
2718
+ slotProps: { select: { displayEmpty: true } },
2719
+ sx: small,
2720
+ children: [/* @__PURE__ */ jsx(MenuItem, {
2721
+ value: "",
2722
+ children: /* @__PURE__ */ jsxs("em", { children: ["padrão", measureMeta?.format ? ` (${FORMAT_LABEL[measureMeta.format]})` : ""] })
2723
+ }), VALUE_FORMATS.map((option) => /* @__PURE__ */ jsx(MenuItem, {
2724
+ value: option,
2725
+ children: FORMAT_LABEL[option]
2726
+ }, option))]
2727
+ })),
2728
+ field("Casas decimais", /* @__PURE__ */ jsx(TextField, {
2729
+ size: "small",
2730
+ fullWidth: true,
2731
+ type: "number",
2732
+ value: decimals ?? "",
2733
+ placeholder: measureMeta?.decimals === void 0 ? "padrão" : String(measureMeta.decimals),
2734
+ error: decimals !== void 0 && (!Number.isInteger(decimals) || decimals < 0 || decimals > 10),
2735
+ onChange: (e) => setFormat("decimals", e.target.value.trim() === "" ? void 0 : Number(e.target.value)),
2736
+ slotProps: { htmlInput: {
2737
+ min: 0,
2738
+ max: 10
2739
+ } },
2740
+ sx: small
2741
+ })),
2742
+ field("Prefixo", /* @__PURE__ */ jsx(TextField, {
2743
+ size: "small",
2744
+ fullWidth: true,
2745
+ value: format.prefix ?? "",
2746
+ placeholder: "ex.: ~",
2747
+ onChange: (e) => setFormat("prefix", e.target.value),
2748
+ sx: small
2749
+ })),
2750
+ field("Sufixo", /* @__PURE__ */ jsx(TextField, {
2751
+ size: "small",
2752
+ fullWidth: true,
2753
+ value: format.suffix ?? "",
2754
+ placeholder: "ex.: t",
2755
+ onChange: (e) => setFormat("suffix", e.target.value),
2756
+ sx: small
2757
+ }))
2758
+ ]
2078
2759
  }),
2079
- /* @__PURE__ */ jsx(Tooltip, {
2080
- title: "Casas decimais (0 a 10)",
2081
- children: /* @__PURE__ */ jsx(TextField, {
2760
+ /* @__PURE__ */ jsx(FormControlLabel, {
2761
+ sx: { m: 0 },
2762
+ control: /* @__PURE__ */ jsx(Checkbox, {
2082
2763
  size: "small",
2083
- type: "number",
2084
- value: decimals ?? "",
2085
- placeholder: fromModel?.decimals === void 0 ? ",00" : String(fromModel.decimals),
2086
- error: decimals !== void 0 && (!Number.isInteger(decimals) || decimals < 0 || decimals > 10),
2087
- onChange: (e) => setFormat(measure, "decimals", e.target.value.trim() === "" ? void 0 : Number(e.target.value)),
2088
- slotProps: { htmlInput: {
2089
- min: 0,
2090
- max: 10
2091
- } },
2092
- sx: small
2764
+ sx: {
2765
+ p: .25,
2766
+ mr: .5
2767
+ },
2768
+ checked: format.compact ?? false,
2769
+ onChange: (e) => setFormat("compact", e.target.checked || void 0)
2770
+ }),
2771
+ label: /* @__PURE__ */ jsx(Typography, {
2772
+ sx: { fontSize: 12 },
2773
+ children: "Notação curta (1,2 mil / 3,4 mi)"
2093
2774
  })
2094
2775
  }),
2095
- /* @__PURE__ */ jsx(TextField, {
2096
- size: "small",
2097
- value: format.prefix ?? "",
2098
- placeholder: "prefixo",
2099
- onChange: (e) => setFormat(measure, "prefix", e.target.value),
2100
- sx: small
2101
- }),
2102
- /* @__PURE__ */ jsx(TextField, {
2103
- size: "small",
2104
- value: format.suffix ?? "",
2105
- placeholder: "sufixo",
2106
- onChange: (e) => setFormat(measure, "suffix", e.target.value),
2107
- sx: small
2108
- }),
2109
- /* @__PURE__ */ jsx(Tooltip, {
2110
- title: "Notação curta: 1,2 mil / 3,4 mi",
2111
- children: /* @__PURE__ */ jsx(FormControlLabel, {
2112
- sx: { m: 0 },
2113
- control: /* @__PURE__ */ jsx(Checkbox, {
2114
- size: "small",
2115
- sx: { p: .25 },
2116
- checked: format.compact ?? false,
2117
- onChange: (e) => setFormat(measure, "compact", e.target.checked || void 0)
2118
- }),
2119
- label: /* @__PURE__ */ jsx(Typography, {
2120
- sx: { fontSize: 11 },
2121
- children: "curto"
2776
+ /* @__PURE__ */ jsxs(Typography, {
2777
+ sx: {
2778
+ fontSize: 11,
2779
+ color: "text.secondary"
2780
+ },
2781
+ children: [
2782
+ "Exemplo:",
2783
+ " ",
2784
+ /* @__PURE__ */ jsx(Box, {
2785
+ component: "span",
2786
+ sx: {
2787
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
2788
+ color: "text.primary"
2789
+ },
2790
+ children: formatMeasure(SAMPLE, resolved, 2)
2122
2791
  })
2123
- })
2792
+ ]
2124
2793
  })
2125
- ]
2126
- })] }, measure);
2127
- })
2128
- });
2794
+ ] }),
2795
+ /* @__PURE__ */ jsx(Box, {
2796
+ sx: {
2797
+ display: "flex",
2798
+ justifyContent: "flex-end"
2799
+ },
2800
+ children: /* @__PURE__ */ jsx(Button, {
2801
+ size: "small",
2802
+ disabled: !customized,
2803
+ onClick: onReset,
2804
+ children: "Restaurar padrão"
2805
+ })
2806
+ })
2807
+ ]
2808
+ })]
2809
+ })] });
2129
2810
  }
2130
2811
  const formGrid = (columns) => ({
2131
2812
  display: "grid",
@@ -2192,59 +2873,219 @@ function NameSelect({ value, options, required, emptyLabel, onChange }) {
2192
2873
  value,
2193
2874
  children: [value, " (não existe)"]
2194
2875
  }),
2195
- 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, {
2196
2877
  value: option.name,
2197
2878
  children: /* @__PURE__ */ jsx(OptionText, { option })
2198
- }, option.name))
2879
+ }, option.name)])
2199
2880
  ]
2200
2881
  });
2201
2882
  }
2202
- /** Lista ordenada de nomes: a ordem de escolha é a ordem das séries/colunas. */
2203
- function MultiNameSelect({ value, options, required, onChange }) {
2204
- const unknown = value.filter((name) => !options.some((option) => option.name === name));
2205
- return /* @__PURE__ */ jsxs(TextField, {
2206
- select: true,
2207
- size: "small",
2208
- fullWidth: true,
2209
- value,
2210
- error: !!unknown.length || required && !value.length,
2211
- onChange: (e) => {
2212
- const next = e.target.value;
2213
- onChange(next);
2883
+ const itemColumn = (item) => item.kind === "percent" ? percentColumn(item.name) : item.name;
2884
+ const KIND_STYLE = {
2885
+ dimension: {
2886
+ icon: "◇",
2887
+ color: "#4dabf7",
2888
+ noun: "dimensão",
2889
+ group: "Dimensões"
2890
+ },
2891
+ measure: {
2892
+ icon: "Σ",
2893
+ color: "#F58220",
2894
+ noun: "medida",
2895
+ group: "Medidas"
2896
+ },
2897
+ percent: {
2898
+ icon: "%",
2899
+ color: "#9775fa",
2900
+ noun: "% do total",
2901
+ group: "% do total (da medida)"
2902
+ }
2903
+ };
2904
+ /**
2905
+ * Lista ordenada, uma linha por item: a ordem é a das séries/colunas. Reordena arrastando pela alça
2906
+ * (⋮⋮) ou com ↑/↓; um único "+ adicionar" no fim (já com o nome escolhido, sem linha vazia). Com
2907
+ * dois tipos (dimensões e medidas juntas), o adicionar agrupa as opções por tipo e a ordem mistura
2908
+ * os dois.
2909
+ */
2910
+ function ColumnList({ items, options, extra, error, onChange }) {
2911
+ const [drag, setDrag] = useState(null);
2912
+ const [grabbed, setGrabbed] = useState(null);
2913
+ const kinds = Object.keys(options);
2914
+ const mixed = kinds.length > 1;
2915
+ const used = (kind, name) => items.some((item) => item.kind === kind && item.name === name);
2916
+ const free = (kind, keep) => (options[kind] ?? []).filter((option) => option.name === keep || !used(kind, option.name));
2917
+ const replace = (index, name) => onChange(items.map((item, i) => i === index ? {
2918
+ ...item,
2919
+ name
2920
+ } : item));
2921
+ const marker = {
2922
+ content: "\"\"",
2923
+ position: "absolute",
2924
+ left: 0,
2925
+ right: 0,
2926
+ height: 2,
2927
+ bgcolor: "primary.main"
2928
+ };
2929
+ const addable = kinds.filter((kind) => free(kind).length);
2930
+ const addOptions = addable.flatMap((kind) => [...mixed ? [/* @__PURE__ */ jsx(ListSubheader, { children: KIND_STYLE[kind].group }, `h:${kind}`)] : [], ...free(kind).map((option) => /* @__PURE__ */ jsxs(MenuItem, {
2931
+ value: `${kind}:${option.name}`,
2932
+ children: [mixed && /* @__PURE__ */ jsx(Box, {
2933
+ component: "span",
2934
+ sx: {
2935
+ color: KIND_STYLE[kind].color,
2936
+ mr: .75
2937
+ },
2938
+ children: KIND_STYLE[kind].icon
2939
+ }), /* @__PURE__ */ jsx(OptionText, { option })]
2940
+ }, `${kind}:${option.name}`))]);
2941
+ const addNoun = mixed ? kinds.map((kind) => KIND_STYLE[kind].noun).join(" ou ") : KIND_STYLE[kinds[0] ?? "measure"].noun;
2942
+ return /* @__PURE__ */ jsxs(Box, {
2943
+ sx: {
2944
+ display: "flex",
2945
+ flexDirection: "column",
2946
+ gap: .5,
2947
+ maxWidth: 760
2214
2948
  },
2215
- slotProps: { select: {
2216
- multiple: true,
2217
- renderValue: (selected) => selected.join(", ") || "(nenhum)",
2218
- displayEmpty: true
2219
- } },
2220
- sx: inputSx$1,
2221
- children: [unknown.map((name) => /* @__PURE__ */ jsxs(MenuItem, {
2222
- value: name,
2223
- children: [name, " (não existe)"]
2224
- }, name)), options.map((option) => /* @__PURE__ */ jsxs(MenuItem, {
2225
- value: option.name,
2226
- dense: true,
2949
+ children: [items.map((item, index) => /* @__PURE__ */ jsxs(Box, {
2950
+ draggable: grabbed === index,
2951
+ onDragStart: (e) => {
2952
+ e.dataTransfer.effectAllowed = "move";
2953
+ e.dataTransfer.setData("text/plain", item.name);
2954
+ setDrag({
2955
+ from: index,
2956
+ to: index
2957
+ });
2958
+ },
2959
+ onDragOver: (e) => {
2960
+ if (!drag) return;
2961
+ e.preventDefault();
2962
+ const rect = e.currentTarget.getBoundingClientRect();
2963
+ const to = e.clientY < rect.top + rect.height / 2 ? index : index + 1;
2964
+ if (to !== drag.to) setDrag({
2965
+ ...drag,
2966
+ to
2967
+ });
2968
+ },
2969
+ onDrop: (e) => {
2970
+ e.preventDefault();
2971
+ if (drag && drag.to !== drag.from && drag.to !== drag.from + 1) onChange(moveTo(items, drag.from, drag.to));
2972
+ setDrag(null);
2973
+ },
2974
+ onDragEnd: () => {
2975
+ setDrag(null);
2976
+ setGrabbed(null);
2977
+ },
2978
+ sx: {
2979
+ position: "relative",
2980
+ display: "flex",
2981
+ gap: .25,
2982
+ alignItems: "center",
2983
+ opacity: drag?.from === index ? .4 : 1,
2984
+ "&:hover .row-actions": { visibility: "visible" },
2985
+ "&::before": drag && drag.from !== index && drag.to === index ? {
2986
+ ...marker,
2987
+ top: -3
2988
+ } : void 0,
2989
+ "&::after": drag && index === items.length - 1 && drag.to === items.length ? {
2990
+ ...marker,
2991
+ bottom: -3
2992
+ } : void 0
2993
+ },
2227
2994
  children: [
2228
- /* @__PURE__ */ jsx(Checkbox, {
2229
- size: "small",
2230
- checked: value.includes(option.name),
2995
+ /* @__PURE__ */ jsx(Box, {
2996
+ component: "span",
2997
+ title: "Arraste para reordenar",
2998
+ onMouseDown: () => setGrabbed(index),
2999
+ onMouseUp: () => setGrabbed(null),
2231
3000
  sx: {
2232
- p: .25,
2233
- mr: 1
2234
- }
3001
+ color: "text.disabled",
3002
+ fontSize: 12,
3003
+ cursor: "grab",
3004
+ userSelect: "none",
3005
+ px: .25
3006
+ },
3007
+ children: "⋮⋮"
2235
3008
  }),
2236
- value.includes(option.name) && /* @__PURE__ */ jsx(Box, {
2237
- component: "span",
3009
+ /* @__PURE__ */ jsx(Typography, {
2238
3010
  sx: {
3011
+ fontFamily: MONO,
3012
+ fontSize: 11,
2239
3013
  color: "primary.main",
2240
- mr: .75,
2241
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
3014
+ minWidth: 14
2242
3015
  },
2243
- children: value.indexOf(option.name) + 1
3016
+ children: index + 1
2244
3017
  }),
2245
- /* @__PURE__ */ jsx(OptionText, { option })
3018
+ mixed && /* @__PURE__ */ jsx(Tooltip, {
3019
+ title: KIND_STYLE[item.kind].noun,
3020
+ children: /* @__PURE__ */ jsx(Box, {
3021
+ component: "span",
3022
+ sx: {
3023
+ color: KIND_STYLE[item.kind].color,
3024
+ fontSize: 12,
3025
+ minWidth: 12
3026
+ },
3027
+ children: KIND_STYLE[item.kind].icon
3028
+ })
3029
+ }),
3030
+ /* @__PURE__ */ jsx(NameSelect, {
3031
+ value: item.name,
3032
+ options: free(item.kind, item.name),
3033
+ required: true,
3034
+ onChange: (next) => replace(index, next)
3035
+ }),
3036
+ /* @__PURE__ */ jsxs(Box, {
3037
+ className: "row-actions",
3038
+ sx: {
3039
+ display: "flex",
3040
+ visibility: "hidden"
3041
+ },
3042
+ children: [
3043
+ /* @__PURE__ */ jsx(SmallButton, {
3044
+ title: "Subir",
3045
+ disabled: index === 0,
3046
+ onClick: () => onChange(moveTo(items, index, index - 1)),
3047
+ children: "↑"
3048
+ }),
3049
+ /* @__PURE__ */ jsx(SmallButton, {
3050
+ title: "Descer",
3051
+ disabled: index === items.length - 1,
3052
+ onClick: () => onChange(moveTo(items, index, index + 2)),
3053
+ children: "↓"
3054
+ }),
3055
+ /* @__PURE__ */ jsx(SmallButton, {
3056
+ title: "Remover",
3057
+ onClick: () => onChange(items.filter((_, i) => i !== index)),
3058
+ children: "×"
3059
+ })
3060
+ ]
3061
+ }),
3062
+ extra?.(item)
2246
3063
  ]
2247
- }, option.name))]
3064
+ }, `${item.kind}:${item.name}`)), addable.length > 0 && /* @__PURE__ */ jsx(TextField, {
3065
+ select: true,
3066
+ size: "small",
3067
+ fullWidth: true,
3068
+ value: "",
3069
+ error,
3070
+ onChange: (e) => {
3071
+ const [kind, ...rest] = e.target.value.split(":");
3072
+ const name = rest.join(":");
3073
+ if (name) onChange([...items, {
3074
+ kind,
3075
+ name
3076
+ }]);
3077
+ },
3078
+ slotProps: { select: {
3079
+ displayEmpty: true,
3080
+ renderValue: () => /* @__PURE__ */ jsxs(Box, {
3081
+ component: "span",
3082
+ sx: { color: "text.secondary" },
3083
+ children: ["+ adicionar ", addNoun]
3084
+ })
3085
+ } },
3086
+ sx: inputSx$1,
3087
+ children: addOptions
3088
+ })]
2248
3089
  });
2249
3090
  }
2250
3091
  //#endregion
@@ -7270,4 +8111,4 @@ function ToggleButton$1({ draft, loading, problems, inspecting, onOpen }) {
7270
8111
  //#endregion
7271
8112
  export { DashboardDevtools as default };
7272
8113
 
7273
- //# sourceMappingURL=DashboardDevtools-7L93aV1-.js.map
8114
+ //# sourceMappingURL=DashboardDevtools-SCewfQGR.js.map