@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.
- package/dist/{DashboardDevtools-7L93aV1-.js → DashboardDevtools-SCewfQGR.js} +1081 -240
- package/dist/DashboardDevtools-SCewfQGR.js.map +1 -0
- package/dist/{ModelDictionary-Y7W9oK1J.js → ModelDictionary-C2tzlOY8.js} +82 -5
- package/dist/ModelDictionary-C2tzlOY8.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +164 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/index.d.ts +32 -3
- package/dist/client/index.js +79 -100
- package/dist/client/index.js.map +1 -1
- package/dist/codegen-DBe4-XW_.js +354 -0
- package/dist/codegen-DBe4-XW_.js.map +1 -0
- package/dist/{types-HXAn-3I1.d.ts → layout-B7eYgNyR.d.ts} +236 -150
- package/dist/{layout-676sEvvb.js → layout-BS51SQcJ.js} +242 -345
- package/dist/layout-BS51SQcJ.js.map +1 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +4 -1
- package/dist/DashboardDevtools-7L93aV1-.js.map +0 -1
- package/dist/ModelDictionary-Y7W9oK1J.js.map +0 -1
- package/dist/layout-676sEvvb.js.map +0 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
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
|
|
4
|
-
import {
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
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
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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":
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
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
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
},
|
|
1847
|
-
|
|
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
|
-
|
|
1856
|
-
|
|
2089
|
+
const button = spec.kind === "dimension" || spec.kind === "measure" ? optionsButton(spec.kind, value) : null;
|
|
2090
|
+
return button ? /* @__PURE__ */ jsxs(Box, {
|
|
1857
2091
|
sx: {
|
|
1858
|
-
|
|
1859
|
-
|
|
2092
|
+
display: "flex",
|
|
2093
|
+
gap: .25,
|
|
2094
|
+
alignItems: "center"
|
|
1860
2095
|
},
|
|
1861
|
-
children:
|
|
1862
|
-
});
|
|
1863
|
-
|
|
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
|
|
1879
|
-
|
|
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:
|
|
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(
|
|
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
|
-
*
|
|
1900
|
-
*
|
|
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
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
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
|
-
/**
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
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(
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
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
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
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
|
-
|
|
2052
|
-
|
|
2637
|
+
fontWeight: 400,
|
|
2638
|
+
ml: 1
|
|
2053
2639
|
},
|
|
2054
|
-
children:
|
|
2055
|
-
})
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
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(
|
|
2080
|
-
|
|
2081
|
-
|
|
2760
|
+
/* @__PURE__ */ jsx(FormControlLabel, {
|
|
2761
|
+
sx: { m: 0 },
|
|
2762
|
+
control: /* @__PURE__ */ jsx(Checkbox, {
|
|
2082
2763
|
size: "small",
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
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__ */
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
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
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
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(
|
|
2229
|
-
|
|
2230
|
-
|
|
2995
|
+
/* @__PURE__ */ jsx(Box, {
|
|
2996
|
+
component: "span",
|
|
2997
|
+
title: "Arraste para reordenar",
|
|
2998
|
+
onMouseDown: () => setGrabbed(index),
|
|
2999
|
+
onMouseUp: () => setGrabbed(null),
|
|
2231
3000
|
sx: {
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
3001
|
+
color: "text.disabled",
|
|
3002
|
+
fontSize: 12,
|
|
3003
|
+
cursor: "grab",
|
|
3004
|
+
userSelect: "none",
|
|
3005
|
+
px: .25
|
|
3006
|
+
},
|
|
3007
|
+
children: "⋮⋮"
|
|
2235
3008
|
}),
|
|
2236
|
-
|
|
2237
|
-
component: "span",
|
|
3009
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
2238
3010
|
sx: {
|
|
3011
|
+
fontFamily: MONO,
|
|
3012
|
+
fontSize: 11,
|
|
2239
3013
|
color: "primary.main",
|
|
2240
|
-
|
|
2241
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
|
|
3014
|
+
minWidth: 14
|
|
2242
3015
|
},
|
|
2243
|
-
children:
|
|
3016
|
+
children: index + 1
|
|
2244
3017
|
}),
|
|
2245
|
-
/* @__PURE__ */ jsx(
|
|
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
|
-
},
|
|
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-
|
|
8114
|
+
//# sourceMappingURL=DashboardDevtools-SCewfQGR.js.map
|