@proef/common-ui 2.2.5 → 2.3.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.
Files changed (27) hide show
  1. package/dist/esm/index.js +6 -0
  2. package/dist/esm/index.js.map +1 -1
  3. package/dist/esm/legacy/components/charts/Charts.js +52 -192
  4. package/dist/esm/legacy/components/charts/Charts.js.map +1 -1
  5. package/dist/esm/legacy/components/molecules/DataTable/DataTable.js +81 -24
  6. package/dist/esm/legacy/components/molecules/DataTable/DataTable.js.map +1 -1
  7. package/dist/esm/legacy/components/organisms/GanttChart/GanttChart.js +7 -4
  8. package/dist/esm/legacy/components/organisms/GanttChart/GanttChart.js.map +1 -1
  9. package/dist/esm/v1/components/data/Charts/Charts.js +301 -0
  10. package/dist/esm/v1/components/data/Charts/Charts.js.map +1 -0
  11. package/dist/esm/v1/components/overlays/Dialog/Dialog.js +4 -1
  12. package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
  13. package/dist/esm/v1/components/tables/Table/ColumnFilter.js +3 -1
  14. package/dist/esm/v1/components/tables/Table/ColumnFilter.js.map +1 -1
  15. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js +3 -1
  16. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js.map +1 -1
  17. package/dist/style.components.css +1 -1
  18. package/dist/style.css +1 -1
  19. package/dist/style.layered.css +1 -1
  20. package/dist/theme.css +28 -0
  21. package/dist/types/legacy/components/charts/Charts.d.ts +14 -11
  22. package/dist/types/legacy/components/molecules/DataTable/DataTable.d.ts +3 -0
  23. package/dist/types/legacy/components/organisms/GanttChart/GanttChart.d.ts +15 -1
  24. package/dist/types/legacy/exports/organisms.d.ts +1 -1
  25. package/dist/types/v1/components/data/Charts/Charts.d.ts +103 -0
  26. package/dist/types/v1/index.d.ts +2 -0
  27. package/package.json +1 -1
package/dist/esm/index.js CHANGED
@@ -94,6 +94,7 @@ import { AvatarStack } from "./v1/primitives/display/AvatarStack/AvatarStack.js"
94
94
  import { AvisosProvider, useAvisos } from "./legacy/components/ui/Avisos.js";
95
95
  import { Badge } from "./v1/primitives/display/Badge/Badge.js";
96
96
  import { BadgeSla, BarraFiltros, Btn, BtnGroup, Campo, CampoInterruptor, CampoPesquisa, CampoSelect, ChipEstadoInterno, Estado, LinhaAlerta, ListaCampos, Modal, SectionGroup } from "./legacy/components/ui/Ui.js";
97
+ import { BarChart, DonutChart, LineChart, shortDateLabel, useChartPalette } from "./v1/components/data/Charts/Charts.js";
97
98
  import { BoldIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, CloseIcon, ErrorIcon, ExternalLinkIcon, FilterIcon, GlobeIcon, ImageIcon, InfoIcon, ItalicIcon, LayersIcon, ListBulletIcon, ListIcon, ListNumberedIcon, LockIcon, LogOutIcon, MaximizeIcon, MenuIcon, MinimizeIcon, MonitorIcon, MoonIcon, PanelOpenIcon, PrefilledIcon, QrCodeIcon, QuoteIcon, SearchIcon, SlidersIcon, SparklesIcon, StrikethroughIcon, SunIcon, UnderlineIcon, WarningIcon } from "./v1/foundations/icons/icons.js";
98
99
  import { Card } from "./v1/primitives/display/Card/Card.js";
99
100
  import { DEFAULT_LIST_FINDER_LABELS } from "./v1/components/lists/ListFinder/labels.js";
@@ -130,6 +131,7 @@ export {
130
131
  AvisosProvider,
131
132
  Badge,
132
133
  BadgeSla,
134
+ BarChart,
133
135
  default7 as BarraAcoes,
134
136
  BarraFiltros,
135
137
  default8 as BarraValor,
@@ -170,6 +172,7 @@ export {
170
172
  DateInput,
171
173
  default20 as Dialog,
172
174
  default21 as DocumentDropzone,
175
+ DonutChart,
173
176
  default22 as Error,
174
177
  ErrorIcon,
175
178
  default23 as ErrorPage,
@@ -208,6 +211,7 @@ export {
208
211
  LayersIcon,
209
212
  default41 as Layout,
210
213
  LazyLoader,
214
+ LineChart,
211
215
  Linha,
212
216
  LinhaAlerta,
213
217
  Linhas,
@@ -311,6 +315,7 @@ export {
311
315
  richTextToPlain,
312
316
  sanitizeRichText,
313
317
  scanQRCodes,
318
+ shortDateLabel,
314
319
  termosDaPesquisa,
315
320
  textToRichText,
316
321
  themeBootScript,
@@ -319,6 +324,7 @@ export {
319
324
  useApi,
320
325
  useAuth,
321
326
  useAvisos,
327
+ useChartPalette,
322
328
  useColumnLayout,
323
329
  useConfirm,
324
330
  useDialogContainer,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,8 +1,9 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { Chart, registerables } from "chart.js";
3
3
  import ChartDataLabels from "chartjs-plugin-datalabels";
4
- import { Doughnut, Bar, Bubble, Line } from "react-chartjs-2";
4
+ import { Bubble } from "react-chartjs-2";
5
5
  import { useIsDark } from "../../../v1/capabilities/theme/useIsDark.js";
6
+ import { DonutChart, BarChart, LineChart } from "../../../v1/components/data/Charts/Charts.js";
6
7
  function euros(v) {
7
8
  if (v == null) return "—";
8
9
  const negativo = v < 0;
@@ -10,12 +11,6 @@ function euros(v) {
10
11
  const comMilhares = inteiro.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
11
12
  return `${negativo ? "-" : ""}${comMilhares},${decimal} €`;
12
13
  }
13
- function labelBucket(b) {
14
- if (/^\d{4}-S\d{2}$/.test(b)) return `S${b.slice(6)}/${b.slice(2, 4)}`;
15
- if (/^\d{4}-\d{2}$/.test(b)) return `${b.slice(5)}/${b.slice(0, 4)}`;
16
- if (/^\d{4}-\d{2}-\d{2}$/.test(b)) return `${b.slice(8)}/${b.slice(5, 7)}`;
17
- return b;
18
- }
19
14
  Chart.register(...registerables, ChartDataLabels);
20
15
  Chart.defaults.set("plugins.datalabels", { display: false });
21
16
  Chart.defaults.font.family = "'Montserrat', sans-serif";
@@ -41,85 +36,39 @@ function useTema() {
41
36
  const escuro = useIsDark();
42
37
  return escuro ? { texto: "#CBD5E1", forte: "#E2E8F0", grelha: "#1E293B" } : { texto: "#5B6577", forte: "#182238", grelha: "#EEF1F5" };
43
38
  }
44
- function legendaBaixo(tema) {
45
- return {
46
- position: "bottom",
47
- labels: { boxWidth: 10, font: { size: 11 }, color: tema.forte, usePointStyle: true, pointStyle: "circle" }
48
- };
49
- }
50
- function clique(labels, series, onClick) {
51
- if (!onClick) return {};
52
- return {
53
- onClick: (_e, els) => {
54
- const el = els[0];
55
- if (!el) return;
56
- onClick(labels[el.index], el.index, series?.[el.datasetIndex]?.nome);
57
- },
58
- onHover: (e, els) => {
59
- const alvo = e.native?.target;
60
- if (alvo) alvo.style.cursor = els.length ? "pointer" : "default";
61
- }
62
- };
63
- }
64
39
  function Barras({
65
- labels: todas,
66
- series: todasSeries,
67
- horizontal = false,
68
- empilhado = false,
40
+ labels,
41
+ series,
42
+ horizontal,
43
+ empilhado,
69
44
  altura,
70
45
  limite,
71
46
  cores,
72
- formatar = (v) => String(v),
73
- valores = true,
74
- percentagem = false,
47
+ formatar,
48
+ valores,
49
+ percentagem,
75
50
  legenda,
76
- formatarEtiqueta = labelBucket,
51
+ formatarEtiqueta,
77
52
  onClick
78
53
  }) {
79
- const tema = useTema();
80
- const labels = limite ? todas.slice(0, limite) : todas;
81
- const series = todasSeries.map((s) => ({ ...s, valores: limite ? s.valores.slice(0, limite) : s.valores }));
82
- const uma = series.length === 1;
83
- const total = uma ? todasSeries[0].valores.reduce((a, b) => a + (b || 0), 0) : 0;
84
- const etiqueta = (v) => percentagem && uma && total > 0 ? `${formatar(v)} (${Math.round(v / total * 100)}%)` : formatar(v);
85
- const alturaFinal = altura ?? (horizontal ? Math.max(200, labels.length * 30 + 40) : 260);
86
- const eixoValor = { beginAtZero: true, stacked: empilhado, grid: { color: tema.grelha }, ticks: { color: tema.texto, callback: (v) => formatar(Number(v)) } };
87
- const eixoEtiqueta = { stacked: empilhado, grid: { display: false }, ticks: { color: tema.texto } };
88
- return /* @__PURE__ */ jsx("div", { style: { height: alturaFinal }, children: /* @__PURE__ */ jsx(
89
- Bar,
54
+ return /* @__PURE__ */ jsx(
55
+ BarChart,
90
56
  {
91
- data: {
92
- labels: labels.map(formatarEtiqueta),
93
- datasets: series.map((s, i) => ({
94
- label: s.nome,
95
- data: s.valores,
96
- backgroundColor: uma && cores ? labels.map((_, j) => cores[j % cores.length]) : s.cor ?? cor(i),
97
- borderRadius: 4,
98
- maxBarThickness: 40
99
- }))
100
- },
101
- options: {
102
- indexAxis: horizontal ? "y" : "x",
103
- maintainAspectRatio: false,
104
- responsive: true,
105
- ...clique(labels, series, onClick),
106
- plugins: {
107
- legend: legenda ?? !uma ? legendaBaixo(tema) : { display: false },
108
- tooltip: { callbacks: { label: (c) => `${c.dataset.label ? `${c.dataset.label}: ` : ""}${etiqueta(Number(c.parsed[horizontal ? "x" : "y"]))}` } },
109
- datalabels: {
110
- display: (c) => valores && !empilhado && Number(c.dataset.data[c.dataIndex]) > 0,
111
- anchor: "end",
112
- align: "end",
113
- color: tema.forte,
114
- font: { size: 10, weight: 600 },
115
- formatter: (v) => etiqueta(v)
116
- }
117
- },
118
- scales: horizontal ? { x: eixoValor, y: eixoEtiqueta } : { x: eixoEtiqueta, y: eixoValor },
119
- layout: { padding: horizontal ? { right: valores ? percentagem ? 56 : 32 : 8 } : { top: valores ? 18 : 4 } }
120
- }
57
+ labels,
58
+ series: series.map((s) => ({ name: s.nome, values: s.valores, color: s.cor })),
59
+ horizontal,
60
+ stacked: empilhado,
61
+ height: altura,
62
+ limit: limite,
63
+ colors: cores,
64
+ format: formatar,
65
+ showValues: valores,
66
+ showPercent: percentagem,
67
+ legend: legenda,
68
+ formatLabel: formatarEtiqueta,
69
+ onClick
121
70
  }
122
- ) });
71
+ );
123
72
  }
124
73
  function BarrasEmpilhadas({ serie, altura = 280, onClick }) {
125
74
  return /* @__PURE__ */ jsx(Barras, { labels: serie.labels, series: serie.series, empilhado: true, altura, onClick, legenda: true });
@@ -148,50 +97,10 @@ function Anel({
148
97
  dados,
149
98
  altura = 260,
150
99
  cores,
151
- formatar = (v) => String(v),
100
+ formatar,
152
101
  onClick
153
102
  }) {
154
- const tema = useTema();
155
- const total = dados.valores.reduce((a, b) => a + (b || 0), 0);
156
- return /* @__PURE__ */ jsx("div", { style: { height: altura }, children: /* @__PURE__ */ jsx(
157
- Doughnut,
158
- {
159
- data: {
160
- labels: dados.labels,
161
- datasets: [
162
- {
163
- data: dados.valores,
164
- backgroundColor: cores ?? dados.labels.map((_, i) => cor(i)),
165
- borderWidth: 0
166
- }
167
- ]
168
- },
169
- options: {
170
- maintainAspectRatio: false,
171
- responsive: true,
172
- cutout: "58%",
173
- ...clique(dados.labels, void 0, onClick),
174
- plugins: {
175
- legend: legendaBaixo(tema),
176
- tooltip: {
177
- callbacks: {
178
- label: (c) => {
179
- const v = Number(c.parsed);
180
- return ` ${c.label}: ${formatar(v)}${total > 0 ? ` (${Math.round(v / total * 100)}%)` : ""}`;
181
- }
182
- }
183
- },
184
- datalabels: {
185
- // Só nas fatias que têm espaço para o número.
186
- display: (c) => total > 0 && Number(c.dataset.data[c.dataIndex]) / total >= 0.05,
187
- color: "#FFFFFF",
188
- font: { size: 11, weight: 700 },
189
- formatter: (v) => formatar(v)
190
- }
191
- }
192
- }
193
- }
194
- ) });
103
+ return /* @__PURE__ */ jsx(DonutChart, { labels: dados.labels, values: dados.valores, height: altura, colors: cores, format: formatar, onClick });
195
104
  }
196
105
  function Linha({
197
106
  dados,
@@ -199,91 +108,42 @@ function Linha({
199
108
  euros: emEuros = false,
200
109
  onClick
201
110
  }) {
202
- const tema = useTema();
203
- return /* @__PURE__ */ jsx("div", { style: { height: altura }, children: /* @__PURE__ */ jsx(
204
- Line,
111
+ return /* @__PURE__ */ jsx(
112
+ LineChart,
205
113
  {
206
- data: {
207
- labels: dados.labels.map(labelBucket),
208
- datasets: [
209
- {
210
- label: "",
211
- data: dados.valores,
212
- borderColor: "#C23B34",
213
- backgroundColor: "rgba(194,59,52,0.12)",
214
- fill: true,
215
- tension: 0.25,
216
- pointRadius: 3
217
- }
218
- ]
219
- },
220
- options: {
221
- maintainAspectRatio: false,
222
- responsive: true,
223
- ...clique(dados.labels, void 0, onClick),
224
- plugins: {
225
- legend: { display: false },
226
- tooltip: {
227
- callbacks: {
228
- label: (c) => emEuros ? euros(Number(c.parsed.y)) : String(c.parsed.y)
229
- }
230
- }
231
- },
232
- scales: {
233
- x: { grid: { display: false }, ticks: { color: tema.texto } },
234
- y: {
235
- beginAtZero: true,
236
- grid: { color: tema.grelha },
237
- ticks: { color: tema.texto, callback: (v) => emEuros ? euros(Number(v)) : String(v) }
238
- }
239
- }
240
- }
114
+ labels: dados.labels,
115
+ series: [{ name: "", values: dados.valores, color: "#C23B34" }],
116
+ area: true,
117
+ height: altura,
118
+ format: emEuros ? (v) => euros(v) : void 0,
119
+ legend: false,
120
+ onClick
241
121
  }
242
- ) });
122
+ );
243
123
  }
244
124
  function Linhas({
245
125
  serie,
246
126
  altura = 280,
247
127
  area = false,
248
128
  cores,
249
- formatar = (v) => String(v),
250
- formatarEtiqueta = labelBucket,
129
+ formatar,
130
+ formatarEtiqueta,
251
131
  onClick
252
132
  }) {
253
- const tema = useTema();
254
- const corDe = (i) => cores?.[i] ?? cor(i);
255
- return /* @__PURE__ */ jsx("div", { style: { height: altura }, children: /* @__PURE__ */ jsx(
256
- Line,
133
+ return /* @__PURE__ */ jsx(
134
+ LineChart,
257
135
  {
258
- data: {
259
- labels: serie.labels.map(formatarEtiqueta),
260
- datasets: serie.series.map((s, i) => ({
261
- label: s.nome,
262
- data: s.valores,
263
- borderColor: corDe(i),
264
- backgroundColor: area ? `${corDe(i)}22` : corDe(i),
265
- fill: area,
266
- tension: 0.25,
267
- pointRadius: serie.labels.length > 24 ? 0 : 3,
268
- pointHoverRadius: 4
269
- }))
270
- },
271
- options: {
272
- maintainAspectRatio: false,
273
- responsive: true,
274
- interaction: { mode: "index", intersect: false },
275
- ...clique(serie.labels, serie.series, onClick),
276
- plugins: {
277
- legend: legendaBaixo(tema),
278
- tooltip: { callbacks: { label: (c) => `${c.dataset.label}: ${formatar(Number(c.parsed.y))}` } }
279
- },
280
- scales: {
281
- x: { grid: { display: false }, ticks: { color: tema.texto, autoSkip: true, maxRotation: 0 } },
282
- y: { beginAtZero: true, grid: { color: tema.grelha }, ticks: { color: tema.texto, callback: (v) => formatar(Number(v)) } }
283
- }
284
- }
136
+ labels: serie.labels,
137
+ series: serie.series.map((s) => ({ name: s.nome, values: s.valores })),
138
+ area,
139
+ colors: cores,
140
+ format: formatar,
141
+ formatLabel: formatarEtiqueta,
142
+ height: altura,
143
+ legend: true,
144
+ onClick
285
145
  }
286
- ) });
146
+ );
287
147
  }
288
148
  function Bolhas({
289
149
  pontos,
@@ -1 +1 @@
1
- {"version":3,"file":"Charts.js","sources":["../../../../../src/legacy/components/charts/Charts.tsx"],"sourcesContent":["import React from 'react'\r\nimport { Chart as ChartJS, registerables, type ActiveElement, type ChartEvent } from 'chart.js'\r\nimport ChartDataLabels from 'chartjs-plugin-datalabels'\r\nimport { Bar, Doughnut, Line, Bubble } from 'react-chartjs-2'\r\nimport { useIsDark } from '../../../v1/capabilities/theme/useIsDark'\r\n\r\n// ---------------------------------------------------------------------------\r\n// Local types (inlined — originally imported from CRE `../types`).\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Stacked series over a set of shared labels (e.g. a dimension over time).\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface SerieEmpilhada {\r\n labels: string[]\r\n series: { nome: string; valores: number[] }[]\r\n}\r\n\r\n/**\r\n * A simple distribution: parallel arrays of labels and values.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface Distribuicao {\r\n labels: string[]\r\n valores: number[]\r\n}\r\n\r\n/**\r\n * Clique num elemento do gráfico: a etiqueta (ex: \"Incidente\"), a posição dela\r\n * e o nome da série (quando há várias). Serve para filtrar ao clicar.\r\n */\r\nexport type CliqueGrafico = (label: string, indice: number, serie?: string) => void\r\n\r\n// ---------------------------------------------------------------------------\r\n// Local formatting helpers (inlined — originally imported from CRE `../lib/format`).\r\n// Behavior kept close to the originals; the library stays self-contained.\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Currency formatter. Thousands separated by '.', decimals by ',', suffixed ' €'.\r\n * Independent of Intl/ICU so the thousands separator is stable across browsers.\r\n */\r\nfunction euros(v: number | null | undefined): string {\r\n if (v == null) return '—'\r\n const negativo = v < 0\r\n const [inteiro, decimal] = Math.abs(v).toFixed(2).split('.')\r\n const comMilhares = inteiro.replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.')\r\n return `${negativo ? '-' : ''}${comMilhares},${decimal} €`\r\n}\r\n\r\n/** 'YYYY-Sxx' / 'YYYY-MM' / 'YYYY-MM-DD' → short axis label. */\r\nfunction labelBucket(b: string): string {\r\n if (/^\\d{4}-S\\d{2}$/.test(b)) return `S${b.slice(6)}/${b.slice(2, 4)}`\r\n if (/^\\d{4}-\\d{2}$/.test(b)) return `${b.slice(5)}/${b.slice(0, 4)}`\r\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(b)) return `${b.slice(8)}/${b.slice(5, 7)}`\r\n return b\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Chart.js registration (once, at module load).\r\n// ---------------------------------------------------------------------------\r\n\r\n// Regista TUDO (registerables inclui Filler, controllers, scales) + o plugin de\r\n// datalabels. O registo parcial degenerava a geometria (doughnut colapsado) e o\r\n// aviso \"fill sem Filler\". registerables = equivalente a chart.js/auto.\r\nChartJS.register(...registerables, ChartDataLabels)\r\n// Datalabels off by default — enabled only where it improves readability.\r\nChartJS.defaults.set('plugins.datalabels', { display: false })\r\nChartJS.defaults.font.family = \"'Montserrat', sans-serif\"\r\nChartJS.defaults.color = '#5B6577'\r\n\r\n/**\r\n * Categorical palette. Stable order so the same dimension always maps to the same color.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport const PALETA = [\r\n '#1868B0',\r\n '#1F8A55',\r\n '#B96E00',\r\n '#C23B34',\r\n '#7C3AED',\r\n '#0891B2',\r\n '#DB2777',\r\n '#65A30D',\r\n '#4B5563',\r\n '#D97706',\r\n '#2563EB',\r\n '#059669',\r\n]\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function cor(i: number): string {\r\n return PALETA[i % PALETA.length]!\r\n}\r\n\r\n/**\r\n * Cores do texto e da grelha conforme o tema: as do claro sobre o fundo escuro\r\n * deixavam os eixos e os valores ilegíveis.\r\n */\r\nfunction useTema() {\r\n const escuro = useIsDark()\r\n return escuro\r\n ? { texto: '#CBD5E1', forte: '#E2E8F0', grelha: '#1E293B' }\r\n : { texto: '#5B6577', forte: '#182238', grelha: '#EEF1F5' }\r\n}\r\n\r\nfunction legendaBaixo(tema: ReturnType<typeof useTema>) {\r\n return {\r\n position: 'bottom' as const,\r\n labels: { boxWidth: 10, font: { size: 11 }, color: tema.forte, usePointStyle: true, pointStyle: 'circle' as const },\r\n }\r\n}\r\n\r\n/** Liga o clique do Chart.js ao `onClick` da biblioteca, com a mão por cima dos elementos. */\r\nfunction clique(labels: string[], series: { nome: string }[] | undefined, onClick?: CliqueGrafico) {\r\n if (!onClick) return {}\r\n return {\r\n onClick: (_e: ChartEvent, els: ActiveElement[]) => {\r\n const el = els[0]\r\n if (!el) return\r\n onClick(labels[el.index]!, el.index, series?.[el.datasetIndex]?.nome)\r\n },\r\n onHover: (e: ChartEvent, els: ActiveElement[]) => {\r\n const alvo = e.native?.target as HTMLElement | undefined\r\n if (alvo) alvo.style.cursor = els.length ? 'pointer' : 'default'\r\n },\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Barras — o componente geral; BarrasEmpilhadas e BarrasHorizontais são atalhos.\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface BarrasProps {\r\n labels: string[]\r\n /** Uma ou várias séries (ex: \"Atribuídos\" e \"Resolvidos\"), lado a lado ou empilhadas. */\r\n series: { nome: string; valores: number[]; cor?: string }[]\r\n /** Barras deitadas, com as etiquetas à esquerda (bom para nomes compridos e rankings). */\r\n horizontal?: boolean\r\n /** Séries empilhadas em vez de lado a lado. */\r\n empilhado?: boolean\r\n /** Altura em px. Nas horizontais, por omissão cresce com o número de barras. */\r\n altura?: number\r\n /** Só as primeiras N (ex: top 10). */\r\n limite?: number\r\n /** Cor de cada barra, quando há uma só série (ex: a cor de cada estado). */\r\n cores?: string[]\r\n /** Formato dos valores no tooltip, nas etiquetas e no eixo (ex: minutos → \"7h30\"). */\r\n formatar?: (v: number) => string\r\n /** Mostra o valor na ponta de cada barra. */\r\n valores?: boolean\r\n /** Junta a percentagem do total ao valor (ex: \"12 (34%)\"); só com uma série. */\r\n percentagem?: boolean\r\n /** Legenda das séries. Por omissão, só quando há mais do que uma. */\r\n legenda?: boolean\r\n /** Formato das etiquetas do eixo (ex: datas). Por omissão, datas ISO ficam curtas. */\r\n formatarEtiqueta?: (label: string) => string\r\n /** Clique numa barra: devolve a etiqueta, a posição e a série. */\r\n onClick?: CliqueGrafico\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Barras({\r\n labels: todas,\r\n series: todasSeries,\r\n horizontal = false,\r\n empilhado = false,\r\n altura,\r\n limite,\r\n cores,\r\n formatar = (v) => String(v),\r\n valores = true,\r\n percentagem = false,\r\n legenda,\r\n formatarEtiqueta = labelBucket,\r\n onClick,\r\n}: BarrasProps) {\r\n const tema = useTema()\r\n const labels = limite ? todas.slice(0, limite) : todas\r\n const series = todasSeries.map((s) => ({ ...s, valores: limite ? s.valores.slice(0, limite) : s.valores }))\r\n const uma = series.length === 1\r\n // A percentagem é do total, não só das barras mostradas (com `limite` seria das top N).\r\n const total = uma ? todasSeries[0]!.valores.reduce((a, b) => a + (b || 0), 0) : 0\r\n const etiqueta = (v: number) => (percentagem && uma && total > 0 ? `${formatar(v)} (${Math.round((v / total) * 100)}%)` : formatar(v))\r\n const alturaFinal = altura ?? (horizontal ? Math.max(200, labels.length * 30 + 40) : 260)\r\n const eixoValor = { beginAtZero: true, stacked: empilhado, grid: { color: tema.grelha }, ticks: { color: tema.texto, callback: (v: string | number) => formatar(Number(v)) } }\r\n const eixoEtiqueta = { stacked: empilhado, grid: { display: false }, ticks: { color: tema.texto } }\r\n\r\n return (\r\n <div style={{ height: alturaFinal }}>\r\n <Bar\r\n data={{\r\n labels: labels.map(formatarEtiqueta),\r\n datasets: series.map((s, i) => ({\r\n label: s.nome,\r\n data: s.valores,\r\n backgroundColor: uma && cores ? labels.map((_, j) => cores[j % cores.length]!) : (s.cor ?? cor(i)),\r\n borderRadius: 4,\r\n maxBarThickness: 40,\r\n })),\r\n }}\r\n options={{\r\n indexAxis: horizontal ? 'y' : 'x',\r\n maintainAspectRatio: false,\r\n responsive: true,\r\n ...clique(labels, series, onClick),\r\n plugins: {\r\n legend: legenda ?? !uma ? legendaBaixo(tema) : { display: false },\r\n tooltip: { callbacks: { label: (c) => `${c.dataset.label ? `${c.dataset.label}: ` : ''}${etiqueta(Number(c.parsed[horizontal ? 'x' : 'y']))}` } },\r\n datalabels: {\r\n display: (c) => valores && !empilhado && Number(c.dataset.data[c.dataIndex]) > 0,\r\n anchor: 'end',\r\n align: 'end',\r\n color: tema.forte,\r\n font: { size: 10, weight: 600 },\r\n formatter: (v: number) => etiqueta(v),\r\n },\r\n },\r\n scales: horizontal ? { x: eixoValor, y: eixoEtiqueta } : { x: eixoEtiqueta, y: eixoValor },\r\n layout: { padding: horizontal ? { right: valores ? (percentagem ? 56 : 32) : 8 } : { top: valores ? 18 : 4 } },\r\n }}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Stacked bars for a dimension over time.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BarrasEmpilhadas({ serie, altura = 280, onClick }: { serie: SerieEmpilhada; altura?: number; onClick?: CliqueGrafico }) {\r\n return <Barras labels={serie.labels} series={serie.series} empilhado altura={altura} onClick={onClick} legenda />\r\n}\r\n\r\n/**\r\n * Horizontal bars of a distribution (top N).\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function BarrasHorizontais({\r\n dados,\r\n altura = 280,\r\n limite = 12,\r\n onClick,\r\n}: {\r\n dados: Distribuicao\r\n altura?: number\r\n limite?: number\r\n onClick?: CliqueGrafico\r\n}) {\r\n const n = Math.min(limite, dados.labels.length)\r\n return (\r\n <Barras\r\n horizontal\r\n labels={dados.labels}\r\n series={[{ nome: '', valores: dados.valores }]}\r\n cores={Array.from({ length: n }, (_, i) => cor(i))}\r\n limite={limite}\r\n altura={altura}\r\n onClick={onClick}\r\n />\r\n )\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function Anel({\r\n dados,\r\n altura = 260,\r\n cores,\r\n formatar = (v) => String(v),\r\n onClick,\r\n}: {\r\n dados: Distribuicao\r\n altura?: number\r\n cores?: string[]\r\n /** Formato dos valores no tooltip. */\r\n formatar?: (v: number) => string\r\n /** Clique numa fatia: devolve a etiqueta e a posição. */\r\n onClick?: CliqueGrafico\r\n}) {\r\n const tema = useTema()\r\n const total = dados.valores.reduce((a, b) => a + (b || 0), 0)\r\n return (\r\n <div style={{ height: altura }}>\r\n <Doughnut\r\n data={{\r\n labels: dados.labels,\r\n datasets: [\r\n {\r\n data: dados.valores,\r\n backgroundColor: cores ?? dados.labels.map((_, i) => cor(i)),\r\n borderWidth: 0,\r\n },\r\n ],\r\n }}\r\n options={{\r\n maintainAspectRatio: false,\r\n responsive: true,\r\n cutout: '58%',\r\n ...clique(dados.labels, undefined, onClick),\r\n plugins: {\r\n legend: legendaBaixo(tema),\r\n tooltip: {\r\n callbacks: {\r\n label: (c) => {\r\n const v = Number(c.parsed)\r\n return ` ${c.label}: ${formatar(v)}${total > 0 ? ` (${Math.round((v / total) * 100)}%)` : ''}`\r\n },\r\n },\r\n },\r\n datalabels: {\r\n // Só nas fatias que têm espaço para o número.\r\n display: (c) => total > 0 && Number(c.dataset.data[c.dataIndex]) / total >= 0.05,\r\n color: '#FFFFFF',\r\n font: { size: 11, weight: 700 },\r\n formatter: (v: number) => formatar(v),\r\n },\r\n },\r\n }}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Simple line chart — optionally formats values as currency.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function Linha({\r\n dados,\r\n altura = 260,\r\n euros: emEuros = false,\r\n onClick,\r\n}: {\r\n dados: Distribuicao\r\n altura?: number\r\n euros?: boolean\r\n onClick?: CliqueGrafico\r\n}) {\r\n const tema = useTema()\r\n return (\r\n <div style={{ height: altura }}>\r\n <Line\r\n data={{\r\n labels: dados.labels.map(labelBucket),\r\n datasets: [\r\n {\r\n label: '',\r\n data: dados.valores,\r\n borderColor: '#C23B34',\r\n backgroundColor: 'rgba(194,59,52,0.12)',\r\n fill: true,\r\n tension: 0.25,\r\n pointRadius: 3,\r\n },\r\n ],\r\n }}\r\n options={{\r\n maintainAspectRatio: false,\r\n responsive: true,\r\n ...clique(dados.labels, undefined, onClick),\r\n plugins: {\r\n legend: { display: false },\r\n tooltip: {\r\n callbacks: {\r\n label: (c) => (emEuros ? euros(Number(c.parsed.y)) : String(c.parsed.y)),\r\n },\r\n },\r\n },\r\n scales: {\r\n x: { grid: { display: false }, ticks: { color: tema.texto } },\r\n y: {\r\n beginAtZero: true,\r\n grid: { color: tema.grelha },\r\n ticks: { color: tema.texto, callback: (v) => (emEuros ? euros(Number(v)) : String(v)) },\r\n },\r\n },\r\n }}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Several lines over the same labels — NOT stacked.\r\n *\r\n * Separate from `BarrasEmpilhadas` on purpose: two series that measure the same\r\n * thing at two moments (created vs closed) must be read against each other, and\r\n * stacking them would draw their sum, which means nothing.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function Linhas({\r\n serie,\r\n altura = 280,\r\n area = false,\r\n cores,\r\n formatar = (v) => String(v),\r\n formatarEtiqueta = labelBucket,\r\n onClick,\r\n}: {\r\n serie: SerieEmpilhada\r\n altura?: number\r\n /// Fill under each line. Only legible with one or two series.\r\n area?: boolean\r\n /** Cor de cada série, pela ordem. */\r\n cores?: string[]\r\n /** Formato dos valores no tooltip e no eixo. */\r\n formatar?: (v: number) => string\r\n /** Formato das etiquetas do eixo (ex: datas). */\r\n formatarEtiqueta?: (label: string) => string\r\n /** Clique num ponto: devolve a etiqueta (ex: o dia) e a série. */\r\n onClick?: CliqueGrafico\r\n}) {\r\n const tema = useTema()\r\n const corDe = (i: number) => cores?.[i] ?? cor(i)\r\n return (\r\n <div style={{ height: altura }}>\r\n <Line\r\n data={{\r\n labels: serie.labels.map(formatarEtiqueta),\r\n datasets: serie.series.map((s, i) => ({\r\n label: s.nome,\r\n data: s.valores,\r\n borderColor: corDe(i),\r\n backgroundColor: area ? `${corDe(i)}22` : corDe(i),\r\n fill: area,\r\n tension: 0.25,\r\n pointRadius: serie.labels.length > 24 ? 0 : 3,\r\n pointHoverRadius: 4,\r\n })),\r\n }}\r\n options={{\r\n maintainAspectRatio: false,\r\n responsive: true,\r\n interaction: { mode: 'index', intersect: false },\r\n ...clique(serie.labels, serie.series, onClick),\r\n plugins: {\r\n legend: legendaBaixo(tema),\r\n tooltip: { callbacks: { label: (c) => `${c.dataset.label}: ${formatar(Number(c.parsed.y))}` } },\r\n },\r\n scales: {\r\n x: { grid: { display: false }, ticks: { color: tema.texto, autoSkip: true, maxRotation: 0 } },\r\n y: { beginAtZero: true, grid: { color: tema.grelha }, ticks: { color: tema.texto, callback: (v) => formatar(Number(v)) } },\r\n },\r\n }}\r\n />\r\n </div>\r\n )\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface PontoRisco {\r\n responsavel: string\r\n otsAtivas: number\r\n percentagemForaPrazo: number\r\n valorPenalizado: number\r\n}\r\n\r\n/**\r\n * Risk-map bubbles: x = active OTs, y = % overdue, r = penalized value.\r\n * The radius is normalized against the max so bubbles stay readable at any scale.\r\n *\r\n * Axis titles and the tooltip formatter are exposed as optional props with\r\n * neutral English defaults (previously hardcoded Portuguese) so the component\r\n * carries no locale of its own.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function Bolhas({\r\n pontos,\r\n altura = 280,\r\n onClick,\r\n xTitle = 'Active OTs',\r\n yTitle = '% overdue',\r\n tooltipLabel = (p) => `${p.responsavel}: ${p.otsAtivas} active, ${p.percentagemForaPrazo}% overdue, ${euros(p.valorPenalizado)}`,\r\n}: {\r\n pontos: PontoRisco[]\r\n altura?: number\r\n onClick?: (p: PontoRisco) => void\r\n xTitle?: string\r\n yTitle?: string\r\n tooltipLabel?: (p: PontoRisco) => string\r\n}) {\r\n const tema = useTema()\r\n const maxValor = Math.max(1, ...pontos.map((p) => p.valorPenalizado))\r\n return (\r\n <div style={{ height: altura }}>\r\n <Bubble\r\n data={{\r\n datasets: pontos.map((p, i) => ({\r\n label: p.responsavel,\r\n data: [\r\n {\r\n x: p.otsAtivas,\r\n y: p.percentagemForaPrazo,\r\n r: 6 + (p.valorPenalizado / maxValor) * 18,\r\n },\r\n ],\r\n backgroundColor: `${cor(i)}CC`,\r\n borderColor: cor(i),\r\n })),\r\n }}\r\n options={{\r\n maintainAspectRatio: false,\r\n responsive: true,\r\n onClick: (_e, elements) => {\r\n const idx = elements[0]?.datasetIndex\r\n if (idx != null && onClick) onClick(pontos[idx]!)\r\n },\r\n plugins: {\r\n legend: { display: false },\r\n tooltip: {\r\n callbacks: {\r\n label: (c) => tooltipLabel(pontos[c.datasetIndex]!),\r\n },\r\n },\r\n },\r\n scales: {\r\n x: { beginAtZero: true, title: { display: true, text: xTitle, color: tema.texto }, grid: { color: tema.grelha }, ticks: { color: tema.texto } },\r\n y: { beginAtZero: true, title: { display: true, text: yTitle, color: tema.texto }, grid: { color: tema.grelha }, ticks: { color: tema.texto } },\r\n },\r\n }}\r\n />\r\n </div>\r\n )\r\n}\r\n"],"names":["ChartJS"],"mappings":"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"}
1
+ {"version":3,"file":"Charts.js","sources":["../../../../../src/legacy/components/charts/Charts.tsx"],"sourcesContent":["import React from 'react'\r\nimport { Chart as ChartJS, registerables } from 'chart.js'\r\nimport ChartDataLabels from 'chartjs-plugin-datalabels'\r\nimport { Bubble } from 'react-chartjs-2'\r\nimport { useIsDark } from '../../../v1/capabilities/theme/useIsDark'\r\nimport { BarChart, DonutChart, LineChart } from '../../../v1/components/data/Charts/Charts'\r\n\r\n// ---------------------------------------------------------------------------\r\n// Local types (inlined — originally imported from CRE `../types`).\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Stacked series over a set of shared labels (e.g. a dimension over time).\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface SerieEmpilhada {\r\n labels: string[]\r\n series: { nome: string; valores: number[] }[]\r\n}\r\n\r\n/**\r\n * A simple distribution: parallel arrays of labels and values.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface Distribuicao {\r\n labels: string[]\r\n valores: number[]\r\n}\r\n\r\n/**\r\n * Clique num elemento do gráfico: a etiqueta (ex: \"Incidente\"), a posição dela\r\n * e o nome da série (quando há várias). Serve para filtrar ao clicar.\r\n */\r\nexport type CliqueGrafico = (label: string, indice: number, serie?: string) => void\r\n\r\n// ---------------------------------------------------------------------------\r\n// Local formatting helpers (inlined — originally imported from CRE `../lib/format`).\r\n// Behavior kept close to the originals; the library stays self-contained.\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Currency formatter. Thousands separated by '.', decimals by ',', suffixed ' €'.\r\n * Independent of Intl/ICU so the thousands separator is stable across browsers.\r\n */\r\nfunction euros(v: number | null | undefined): string {\r\n if (v == null) return '—'\r\n const negativo = v < 0\r\n const [inteiro, decimal] = Math.abs(v).toFixed(2).split('.')\r\n const comMilhares = inteiro.replace(/\\B(?=(\\d{3})+(?!\\d))/g, '.')\r\n return `${negativo ? '-' : ''}${comMilhares},${decimal} €`\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Chart.js registration (once, at module load).\r\n// ---------------------------------------------------------------------------\r\n\r\n// Regista TUDO (registerables inclui Filler, controllers, scales) + o plugin de\r\n// datalabels. O registo parcial degenerava a geometria (doughnut colapsado) e o\r\n// aviso \"fill sem Filler\". registerables = equivalente a chart.js/auto.\r\nChartJS.register(...registerables, ChartDataLabels)\r\n// Datalabels off by default — enabled only where it improves readability.\r\nChartJS.defaults.set('plugins.datalabels', { display: false })\r\nChartJS.defaults.font.family = \"'Montserrat', sans-serif\"\r\nChartJS.defaults.color = '#5B6577'\r\n\r\n/**\r\n * Categorical palette. Stable order so the same dimension always maps to the same color.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport const PALETA = [\r\n '#1868B0',\r\n '#1F8A55',\r\n '#B96E00',\r\n '#C23B34',\r\n '#7C3AED',\r\n '#0891B2',\r\n '#DB2777',\r\n '#65A30D',\r\n '#4B5563',\r\n '#D97706',\r\n '#2563EB',\r\n '#059669',\r\n]\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport function cor(i: number): string {\r\n return PALETA[i % PALETA.length]!\r\n}\r\n\r\n/**\r\n * Cores do texto e da grelha conforme o tema: as do claro sobre o fundo escuro\r\n * deixavam os eixos e os valores ilegíveis.\r\n */\r\nfunction useTema() {\r\n const escuro = useIsDark()\r\n return escuro\r\n ? { texto: '#CBD5E1', forte: '#E2E8F0', grelha: '#1E293B' }\r\n : { texto: '#5B6577', forte: '#182238', grelha: '#EEF1F5' }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Barras, Anel e Linhas: adaptadores dos gráficos V1 (BarChart, DonutChart, LineChart),\r\n// com as props de sempre. Só as Bolhas continuam desenhadas aqui.\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface BarrasProps {\r\n labels: string[]\r\n /** Uma ou várias séries (ex: \"Atribuídos\" e \"Resolvidos\"), lado a lado ou empilhadas. */\r\n series: { nome: string; valores: number[]; cor?: string }[]\r\n /** Barras deitadas, com as etiquetas à esquerda (bom para nomes compridos e rankings). */\r\n horizontal?: boolean\r\n /** Séries empilhadas em vez de lado a lado. */\r\n empilhado?: boolean\r\n /** Altura em px. Nas horizontais, por omissão cresce com o número de barras. */\r\n altura?: number\r\n /** Só as primeiras N (ex: top 10). */\r\n limite?: number\r\n /** Cor de cada barra, quando há uma só série (ex: a cor de cada estado). */\r\n cores?: string[]\r\n /** Formato dos valores no tooltip, nas etiquetas e no eixo (ex: minutos → \"7h30\"). */\r\n formatar?: (v: number) => string\r\n /** Mostra o valor na ponta de cada barra. */\r\n valores?: boolean\r\n /** Junta a percentagem do total ao valor (ex: \"12 (34%)\"); só com uma série. */\r\n percentagem?: boolean\r\n /** Legenda das séries. Por omissão, só quando há mais do que uma. */\r\n legenda?: boolean\r\n /** Formato das etiquetas do eixo (ex: datas). Por omissão, datas ISO ficam curtas. */\r\n formatarEtiqueta?: (label: string) => string\r\n /** Clique numa barra: devolve a etiqueta, a posição e a série. */\r\n onClick?: CliqueGrafico\r\n}\r\n\r\n/**\r\n * Migrado para o V1: adaptador do `BarChart` (mesmas props de sempre).\r\n * @deprecated Legacy — usar o `BarChart` do V1. Ver Storybook › Legacy.\r\n */\r\nexport function Barras({\r\n labels,\r\n series,\r\n horizontal,\r\n empilhado,\r\n altura,\r\n limite,\r\n cores,\r\n formatar,\r\n valores,\r\n percentagem,\r\n legenda,\r\n formatarEtiqueta,\r\n onClick,\r\n}: BarrasProps) {\r\n return (\r\n <BarChart\r\n labels={labels}\r\n series={series.map((s) => ({ name: s.nome, values: s.valores, color: s.cor }))}\r\n horizontal={horizontal}\r\n stacked={empilhado}\r\n height={altura}\r\n limit={limite}\r\n colors={cores}\r\n format={formatar}\r\n showValues={valores}\r\n showPercent={percentagem}\r\n legend={legenda}\r\n formatLabel={formatarEtiqueta}\r\n onClick={onClick}\r\n />\r\n )\r\n}\r\n\r\n/**\r\n * Stacked bars for a dimension over time.\r\n * @deprecated Legacy — usar o `BarChart` do V1 com `stacked`. Ver Storybook › Legacy.\r\n */\r\nexport function BarrasEmpilhadas({ serie, altura = 280, onClick }: { serie: SerieEmpilhada; altura?: number; onClick?: CliqueGrafico }) {\r\n return <Barras labels={serie.labels} series={serie.series} empilhado altura={altura} onClick={onClick} legenda />\r\n}\r\n\r\n/**\r\n * Horizontal bars of a distribution (top N).\r\n * @deprecated Legacy — usar o `BarChart` do V1 com `horizontal`. Ver Storybook › Legacy.\r\n */\r\nexport function BarrasHorizontais({\r\n dados,\r\n altura = 280,\r\n limite = 12,\r\n onClick,\r\n}: {\r\n dados: Distribuicao\r\n altura?: number\r\n limite?: number\r\n onClick?: CliqueGrafico\r\n}) {\r\n const n = Math.min(limite, dados.labels.length)\r\n return (\r\n <Barras\r\n horizontal\r\n labels={dados.labels}\r\n series={[{ nome: '', valores: dados.valores }]}\r\n cores={Array.from({ length: n }, (_, i) => cor(i))}\r\n limite={limite}\r\n altura={altura}\r\n onClick={onClick}\r\n />\r\n )\r\n}\r\n\r\n/**\r\n * Migrado para o V1: adaptador do `DonutChart`.\r\n * @deprecated Legacy — usar o `DonutChart` do V1. Ver Storybook › Legacy.\r\n */\r\nexport function Anel({\r\n dados,\r\n altura = 260,\r\n cores,\r\n formatar,\r\n onClick,\r\n}: {\r\n dados: Distribuicao\r\n altura?: number\r\n cores?: string[]\r\n /** Formato dos valores no tooltip. */\r\n formatar?: (v: number) => string\r\n /** Clique numa fatia: devolve a etiqueta e a posição. */\r\n onClick?: CliqueGrafico\r\n}) {\r\n return <DonutChart labels={dados.labels} values={dados.valores} height={altura} colors={cores} format={formatar} onClick={onClick} />\r\n}\r\n\r\n/**\r\n * Simple line chart — optionally formats values as currency.\r\n * @deprecated Legacy — usar o `LineChart` do V1. Ver Storybook › Legacy.\r\n */\r\nexport function Linha({\r\n dados,\r\n altura = 260,\r\n euros: emEuros = false,\r\n onClick,\r\n}: {\r\n dados: Distribuicao\r\n altura?: number\r\n euros?: boolean\r\n onClick?: CliqueGrafico\r\n}) {\r\n return (\r\n <LineChart\r\n labels={dados.labels}\r\n series={[{ name: '', values: dados.valores, color: '#C23B34' }]}\r\n area\r\n height={altura}\r\n format={emEuros ? (v) => euros(v) : undefined}\r\n legend={false}\r\n onClick={onClick}\r\n />\r\n )\r\n}\r\n\r\n/**\r\n * Several lines over the same labels — NOT stacked.\r\n * Migrado para o V1: adaptador do `LineChart` (com a legenda sempre, como antes).\r\n * @deprecated Legacy — usar o `LineChart` do V1. Ver Storybook › Legacy.\r\n */\r\nexport function Linhas({\r\n serie,\r\n altura = 280,\r\n area = false,\r\n cores,\r\n formatar,\r\n formatarEtiqueta,\r\n onClick,\r\n}: {\r\n serie: SerieEmpilhada\r\n altura?: number\r\n /// Fill under each line. Only legible with one or two series.\r\n area?: boolean\r\n /** Cor de cada série, pela ordem. */\r\n cores?: string[]\r\n /** Formato dos valores no tooltip e no eixo. */\r\n formatar?: (v: number) => string\r\n /** Formato das etiquetas do eixo (ex: datas). */\r\n formatarEtiqueta?: (label: string) => string\r\n /** Clique num ponto: devolve a etiqueta (ex: o dia) e a série. */\r\n onClick?: CliqueGrafico\r\n}) {\r\n return (\r\n <LineChart\r\n labels={serie.labels}\r\n series={serie.series.map((s) => ({ name: s.nome, values: s.valores }))}\r\n area={area}\r\n colors={cores}\r\n format={formatar}\r\n formatLabel={formatarEtiqueta}\r\n height={altura}\r\n legend\r\n onClick={onClick}\r\n />\r\n )\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface PontoRisco {\r\n responsavel: string\r\n otsAtivas: number\r\n percentagemForaPrazo: number\r\n valorPenalizado: number\r\n}\r\n\r\n/**\r\n * Risk-map bubbles: x = active OTs, y = % overdue, r = penalized value.\r\n * The radius is normalized against the max so bubbles stay readable at any scale.\r\n *\r\n * Axis titles and the tooltip formatter are exposed as optional props with\r\n * neutral English defaults (previously hardcoded Portuguese) so the component\r\n * carries no locale of its own.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function Bolhas({\r\n pontos,\r\n altura = 280,\r\n onClick,\r\n xTitle = 'Active OTs',\r\n yTitle = '% overdue',\r\n tooltipLabel = (p) => `${p.responsavel}: ${p.otsAtivas} active, ${p.percentagemForaPrazo}% overdue, ${euros(p.valorPenalizado)}`,\r\n}: {\r\n pontos: PontoRisco[]\r\n altura?: number\r\n onClick?: (p: PontoRisco) => void\r\n xTitle?: string\r\n yTitle?: string\r\n tooltipLabel?: (p: PontoRisco) => string\r\n}) {\r\n const tema = useTema()\r\n const maxValor = Math.max(1, ...pontos.map((p) => p.valorPenalizado))\r\n return (\r\n <div style={{ height: altura }}>\r\n <Bubble\r\n data={{\r\n datasets: pontos.map((p, i) => ({\r\n label: p.responsavel,\r\n data: [\r\n {\r\n x: p.otsAtivas,\r\n y: p.percentagemForaPrazo,\r\n r: 6 + (p.valorPenalizado / maxValor) * 18,\r\n },\r\n ],\r\n backgroundColor: `${cor(i)}CC`,\r\n borderColor: cor(i),\r\n })),\r\n }}\r\n options={{\r\n maintainAspectRatio: false,\r\n responsive: true,\r\n onClick: (_e, elements) => {\r\n const idx = elements[0]?.datasetIndex\r\n if (idx != null && onClick) onClick(pontos[idx]!)\r\n },\r\n plugins: {\r\n legend: { display: false },\r\n tooltip: {\r\n callbacks: {\r\n label: (c) => tooltipLabel(pontos[c.datasetIndex]!),\r\n },\r\n },\r\n },\r\n scales: {\r\n x: { beginAtZero: true, title: { display: true, text: xTitle, color: tema.texto }, grid: { color: tema.grelha }, ticks: { color: tema.texto } },\r\n y: { beginAtZero: true, title: { display: true, text: yTitle, color: tema.texto }, grid: { color: tema.grelha }, ticks: { color: tema.texto } },\r\n },\r\n }}\r\n />\r\n </div>\r\n )\r\n}\r\n"],"names":["ChartJS"],"mappings":"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"}
@@ -37,7 +37,9 @@ const DEFAULT_LABELS = {
37
37
  clearGroups: "Clear grouping",
38
38
  perPage: "Per page",
39
39
  selectAll: "(Select all)",
40
- selectAllResults: "(Select results)"
40
+ selectAllResults: "(Select results)",
41
+ cancel: "Cancel",
42
+ confirm: "Confirm"
41
43
  };
42
44
  const Funnel = ({ size = 12, className }) => /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", className, children: /* @__PURE__ */ jsx("path", { d: "M22 3H2l8 9.46V19l4 2v-8.54L22 3z" }) });
43
45
  const Layers = ({ size = 11, className }) => /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", className, children: [
@@ -136,14 +138,18 @@ function valorParaIntervalo(row, col) {
136
138
  const v = rawValue(row, col);
137
139
  return col.sortType === "date" ? timestampDeData(v) : Number(v);
138
140
  }
141
+ function csvValue(row, col) {
142
+ const v = rawValue(row, col);
143
+ if (v == null) return "";
144
+ if (typeof v === "number") return Number.isInteger(v) ? String(v) : String(v).replace(".", ",");
145
+ return v === "[object Object]" ? "" : v;
146
+ }
139
147
  function toCsv(columns, rows) {
140
- const esc = (v) => {
141
- const s = v == null ? "" : String(v);
142
- return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
143
- };
144
- const head = columns.map((c) => esc(c.label)).join(",");
145
- const body = rows.map((r) => columns.map((c) => esc(rawValue(r, c))).join(",")).join("\n");
146
- return `${head}
148
+ const esc = (s) => /[";\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
149
+ const cols = columns.filter((c) => rows.some((r) => csvValue(r, c) !== ""));
150
+ const head = cols.map((c) => esc(c.label)).join(";");
151
+ const body = rows.map((r) => cols.map((c) => esc(csvValue(r, c))).join(";")).join("\r\n");
152
+ return `${head}\r
147
153
  ${body}`;
148
154
  }
149
155
  const ordenarRotulos = (a, b) => a === EMPTY ? 1 : b === EMPTY ? -1 : COLLATOR.compare(a, b);
@@ -308,6 +314,10 @@ function DataTable({
308
314
  const [selectedKeys, setSelectedKeys] = useState(() => new Set(selecaoInicial ?? []));
309
315
  const [openFilter, setOpenFilter] = useState(null);
310
316
  const [filterSearch, setFilterSearch] = useState("");
317
+ const [rascunho, setRascunho] = useState(null);
318
+ useEffect(() => {
319
+ setRascunho(null);
320
+ }, [openFilter]);
311
321
  const filterRefs = useRef(/* @__PURE__ */ new Map());
312
322
  const popupRef = useRef(null);
313
323
  const [popupPos, setPopupPos] = useState(null);
@@ -666,8 +676,17 @@ function DataTable({
666
676
  if (!allData) onDataNeeded?.();
667
677
  setSort((s) => s?.key === key ? s.dir === 1 ? { key, dir: -1 } : null : { key, dir: 1 });
668
678
  };
669
- const selecaoDaColuna = (key) => new Set(columnFilters[key] ?? (modoExcel ? openFilterOptions : []));
670
- const aplicarSelecao = (key, sel) => mudarFiltrosCol((prev) => {
679
+ const selecaoDaColuna = (key) => new Set(rascunho?.key === key ? rascunho.sel : columnFilters[key] ?? (modoExcel ? openFilterOptions : []));
680
+ const aplicarSelecao = (key, sel) => setRascunho({ key, sel });
681
+ const confirmarSelecao = (key) => {
682
+ if (rascunho?.key === key) {
683
+ if (!allData) onDataNeeded?.();
684
+ gravarSelecao(key, rascunho.sel);
685
+ }
686
+ setOpenFilter(null);
687
+ setFilterSearch("");
688
+ };
689
+ const gravarSelecao = (key, sel) => mudarFiltrosCol((prev) => {
671
690
  const proximo = { ...prev };
672
691
  if (modoExcel && sel.size === openFilterOptions.length) delete proximo[key];
673
692
  else proximo[key] = sel;
@@ -703,6 +722,7 @@ function DataTable({
703
722
  });
704
723
  };
705
724
  const clearColumnFilter = (key) => {
725
+ setRascunho(null);
706
726
  mudarFiltrosCol((prev) => {
707
727
  const n = { ...prev };
708
728
  delete n[key];
@@ -724,8 +744,9 @@ function DataTable({
724
744
  };
725
745
  const temFiltro = (key) => (modoExcel ? columnFilters[key] !== void 0 : (columnFilters[key]?.size ?? 0) > 0) || temIntervalo(key);
726
746
  const handleExport = () => {
727
- if (onExport) return onExport(sorted);
728
- const csv = toCsv(columns, sorted);
747
+ const linhas = allData && !isClientMode ? ordenar(filtrar(allData)) : sorted;
748
+ if (onExport) return onExport(linhas);
749
+ const csv = toCsv(columns, linhas);
729
750
  const blob = new Blob([`\uFEFF${csv}`], { type: "text/csv;charset=utf-8;" });
730
751
  const url = URL.createObjectURL(blob);
731
752
  const a = document.createElement("a");
@@ -789,7 +810,7 @@ function DataTable({
789
810
  const renderPopup = (coluna, key, listas) => {
790
811
  const col = key === coluna.key ? coluna : void 0;
791
812
  const { opts, total: totalOpcoes } = opcoesVisiveis;
792
- const sel = columnFilters[key];
813
+ const sel = rascunho?.key === key ? rascunho.sel : columnFilters[key];
793
814
  const isFiltered = temFiltro(key);
794
815
  const podeOrdenar = !!sortFromFilter && !!col && col.sortable !== false;
795
816
  const eData = col?.sortType === "date";
@@ -898,6 +919,12 @@ function DataTable({
898
919
  autoFocus: true,
899
920
  value: filterSearch,
900
921
  onChange: (e) => setFilterSearch(e.target.value),
922
+ onKeyDown: (e) => {
923
+ if (e.key === "Enter") {
924
+ e.preventDefault();
925
+ confirmarSelecao(key);
926
+ }
927
+ },
901
928
  placeholder: L.search,
902
929
  className: "w-full bg-slate-50 dark:bg-slate-800 dark:text-slate-100 border border-slate-200 dark:border-slate-700 rounded-lg px-2 py-1.5 text-xs outline-none focus:border-proef-blue dark:focus:border-proef-turquoise mb-2"
903
930
  }
@@ -951,14 +978,38 @@ function DataTable({
951
978
  })
952
979
  ] }),
953
980
  totalOpcoes > opts.length && /* @__PURE__ */ jsx("p", { className: "text-[10.5px] text-slate-400 text-center pt-2", children: L.moreOptions.replace("{shown}", String(opts.length)).replace("{total}", String(totalOpcoes)) }),
954
- isFiltered && /* @__PURE__ */ jsx(
955
- "button",
956
- {
957
- onClick: () => clearColumnFilter(key),
958
- className: "appearance-none bg-transparent border-0 border-t border-slate-100 dark:border-slate-800 cursor-pointer w-full mt-2 pt-2 text-[10px] font-black uppercase tracking-wider text-red-400 hover:text-red-500",
959
- children: L.clear
960
- }
961
- )
981
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2 pt-2 border-t border-slate-100 dark:border-slate-800", children: [
982
+ isFiltered && /* @__PURE__ */ jsx(
983
+ "button",
984
+ {
985
+ type: "button",
986
+ onClick: () => clearColumnFilter(key),
987
+ className: "appearance-none bg-transparent border-0 cursor-pointer text-[10px] font-black uppercase tracking-wider text-red-400 hover:text-red-500",
988
+ children: L.clear
989
+ }
990
+ ),
991
+ /* @__PURE__ */ jsx(
992
+ "button",
993
+ {
994
+ type: "button",
995
+ onClick: () => {
996
+ setOpenFilter(null);
997
+ setFilterSearch("");
998
+ },
999
+ className: "appearance-none bg-transparent border-0 cursor-pointer ml-auto text-[10px] font-black uppercase tracking-wider text-slate-400 hover:text-slate-600 dark:hover:text-slate-200",
1000
+ children: L.cancel
1001
+ }
1002
+ ),
1003
+ /* @__PURE__ */ jsx(
1004
+ "button",
1005
+ {
1006
+ type: "button",
1007
+ onClick: () => confirmarSelecao(key),
1008
+ className: "appearance-none border-0 cursor-pointer rounded-lg bg-proef-navy dark:bg-proef-turquoise px-3 py-1.5 text-[10px] font-black uppercase tracking-wider text-white dark:text-proef-navy hover:bg-proef-blue dark:hover:opacity-90",
1009
+ children: L.confirm
1010
+ }
1011
+ )
1012
+ ] })
962
1013
  ] })
963
1014
  ]
964
1015
  }
@@ -1085,13 +1136,19 @@ function DataTable({
1085
1136
  /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-500 dark:text-slate-400", children: isLoading ? "" : L.results.replace("{count}", String(totalResultados)) }),
1086
1137
  /* @__PURE__ */ jsxs("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-2", children: [
1087
1138
  extraToolbar,
1088
- exportFileName && sorted.length > 0 && /* @__PURE__ */ jsxs(
1139
+ exportFileName && sorted.length > 0 && // Mesmo aspeto do botão Colunas ao lado (o navy cheio competia com as ações da página).
1140
+ /* @__PURE__ */ jsxs(
1089
1141
  "button",
1090
1142
  {
1143
+ type: "button",
1091
1144
  onClick: handleExport,
1092
- className: "appearance-none cursor-pointer inline-flex items-center gap-1.5 h-8 px-3.5 rounded-lg border border-proef-navy dark:border-proef-turquoise bg-proef-navy dark:bg-proef-turquoise text-white dark:text-proef-navy hover:bg-proef-blue dark:hover:bg-proef-turquoise/80 text-[11px] font-black uppercase tracking-widest transition-colors",
1145
+ className: "appearance-none cursor-pointer bg-white inline-flex items-center gap-2 h-8 rounded-lg border border-slate-300 px-3 text-xs font-medium text-slate-700 transition hover:bg-slate-50 dark:bg-slate-800 dark:border-slate-600 dark:text-slate-200 dark:hover:bg-slate-700",
1093
1146
  children: [
1094
- "⭳ ",
1147
+ /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: "h-4 w-4", "aria-hidden": true, children: [
1148
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
1149
+ /* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
1150
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
1151
+ ] }),
1095
1152
  L.export
1096
1153
  ]
1097
1154
  }