@proef/common-ui 2.2.6 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) 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 +252 -115
  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/capabilities/overlay/useFloatingPanel.js +5 -2
  10. package/dist/esm/v1/capabilities/overlay/useFloatingPanel.js.map +1 -1
  11. package/dist/esm/v1/components/data/Charts/Charts.js +301 -0
  12. package/dist/esm/v1/components/data/Charts/Charts.js.map +1 -0
  13. package/dist/esm/v1/components/data/Pagination/Pagination.js +30 -26
  14. package/dist/esm/v1/components/data/Pagination/Pagination.js.map +1 -1
  15. package/dist/esm/v1/components/forms/FormField/FormField.js +3 -2
  16. package/dist/esm/v1/components/forms/FormField/FormField.js.map +1 -1
  17. package/dist/esm/v1/components/layout/AppShell/AppShell.js +8 -3
  18. package/dist/esm/v1/components/layout/AppShell/AppShell.js.map +1 -1
  19. package/dist/esm/v1/components/layout/Page/Page.js +1 -1
  20. package/dist/esm/v1/components/layout/Page/Page.js.map +1 -1
  21. package/dist/esm/v1/components/layout/PageHeader/PageHeader.js +21 -18
  22. package/dist/esm/v1/components/layout/PageHeader/PageHeader.js.map +1 -1
  23. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js +16 -16
  24. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js.map +1 -1
  25. package/dist/esm/v1/components/overlays/Dialog/Dialog.js +5 -2
  26. package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
  27. package/dist/esm/v1/components/tables/Table/ColumnFilter.js +3 -1
  28. package/dist/esm/v1/components/tables/Table/ColumnFilter.js.map +1 -1
  29. package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js +3 -3
  30. package/dist/esm/v1/primitives/buttons/SegmentedControl/SegmentedControl.js.map +1 -1
  31. package/dist/esm/v1/primitives/display/Kpi/Kpi.js +6 -3
  32. package/dist/esm/v1/primitives/display/Kpi/Kpi.js.map +1 -1
  33. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js +31 -9
  34. package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js.map +1 -1
  35. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js +5 -5
  36. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js.map +1 -1
  37. package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js +97 -36
  38. package/dist/esm/v1/primitives/navigation/Tabs/Tabs.js.map +1 -1
  39. package/dist/style.components.css +1 -1
  40. package/dist/style.css +1 -1
  41. package/dist/style.layered.css +1 -1
  42. package/dist/theme.css +28 -0
  43. package/dist/types/legacy/components/charts/Charts.d.ts +14 -11
  44. package/dist/types/legacy/components/molecules/DataTable/DataTable.d.ts +12 -1
  45. package/dist/types/legacy/components/organisms/GanttChart/GanttChart.d.ts +15 -1
  46. package/dist/types/legacy/exports/organisms.d.ts +1 -1
  47. package/dist/types/v1/components/data/Charts/Charts.d.ts +103 -0
  48. package/dist/types/v1/index.d.ts +2 -0
  49. package/dist/types/v1/primitives/forms/SelectInput/SelectInput.d.ts +12 -0
  50. package/dist/types/v1/primitives/lists/ListItem/ListItem.d.ts +1 -1
  51. package/package.json +1 -1
@@ -15,6 +15,8 @@ function GanttChart({
15
15
  expanded: expandedProp,
16
16
  onExpandedChange,
17
17
  renderTooltip,
18
+ onMarkerClick,
19
+ onRowClick,
18
20
  monthNames = MESES_PT,
19
21
  emptyText = "—",
20
22
  className = ""
@@ -59,8 +61,9 @@ function GanttChart({
59
61
  marcas.map((m) => /* @__PURE__ */ jsx(
60
62
  "div",
61
63
  {
62
- className: "absolute top-1/2 w-[9px] h-[9px] bg-slate-900 border-[1.5px] border-white",
64
+ className: `absolute top-1/2 w-[9px] h-[9px] bg-slate-900 border-[1.5px] border-white ${m.onClick ? "cursor-pointer" : ""}`,
63
65
  style: { left: m.i * dayWidth + dayWidth / 2, transform: "translate(-50%,-50%) rotate(45deg)" },
66
+ onClick: m.onClick,
64
67
  onMouseMove: (e) => mostrarTip(e, m.tip),
65
68
  onMouseLeave: () => setTip(null)
66
69
  },
@@ -95,7 +98,7 @@ function GanttChart({
95
98
  tip: renderTooltip?.({ row, day: d, kind: "day", count: n })
96
99
  }));
97
100
  }
98
- const marcas = (row.markers ?? []).map((d) => ({ i: toIdx(d, t0), tip: renderTooltip?.({ row, day: d, kind: "marker" }) }));
101
+ const marcas = (row.markers ?? []).map((d) => ({ i: toIdx(d, t0), tip: renderTooltip?.({ row, day: d, kind: "marker" }), onClick: onMarkerClick && (() => onMarkerClick({ row, day: d })) }));
99
102
  linhas.push(
100
103
  /* @__PURE__ */ jsxs("div", { className: "group flex min-h-[22px] border-b border-slate-200 hover:bg-blue-50/60", children: [
101
104
  /* @__PURE__ */ jsxs(
@@ -119,10 +122,10 @@ function GanttChart({
119
122
  if (aberto)
120
123
  for (const f of filhos) {
121
124
  const b = (f.days ?? []).map((d) => ({ i: toIdx(d, t0), color: f.color ?? ACCENT, tip: renderTooltip?.({ row: f, day: d, kind: "day", parent: row }) }));
122
- const m = (f.markers ?? []).map((d) => ({ i: toIdx(d, t0), tip: renderTooltip?.({ row: f, day: d, kind: "marker", parent: row }) }));
125
+ const m = (f.markers ?? []).map((d) => ({ i: toIdx(d, t0), tip: renderTooltip?.({ row: f, day: d, kind: "marker", parent: row }), onClick: onMarkerClick && (() => onMarkerClick({ row: f, day: d, parent: row })) }));
123
126
  linhas.push(
124
127
  /* @__PURE__ */ jsxs("div", { className: "group flex min-h-[22px] border-b border-slate-200 hover:bg-blue-50/60", children: [
125
- /* @__PURE__ */ jsxs("div", { className: "sticky left-0 z-[2] shrink-0 truncate border-r border-slate-200 bg-white py-[3px] pl-7 pr-2 text-slate-500 group-hover:bg-blue-50", style: { width: labelWidth, color: f.labelColor }, title: f.warning ? `${f.label} — ${f.warning}` : f.label, children: [
128
+ /* @__PURE__ */ jsxs("div", { className: `sticky left-0 z-[2] shrink-0 truncate border-r border-slate-200 bg-white py-[3px] pl-7 pr-2 text-slate-500 group-hover:bg-blue-50 ${onRowClick ? "cursor-pointer" : ""}`, style: { width: labelWidth, color: f.labelColor }, onClick: onRowClick ? () => onRowClick({ row: f, parent: row }) : void 0, title: f.warning ? `${f.label} — ${f.warning}` : f.label, children: [
126
129
  f.label,
127
130
  f.sublabel && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[11px] text-slate-400", children: f.sublabel }),
128
131
  f.warning && /* @__PURE__ */ jsx("span", { className: "ml-1 text-orange-600", children: "⚠" })
@@ -1 +1 @@
1
- {"version":3,"file":"GanttChart.js","sources":["../../../../../../src/legacy/components/organisms/GanttChart/GanttChart.tsx"],"sourcesContent":["import React, { Fragment, useMemo, useState } from 'react'\r\n\r\n// ─── Tipos ──────────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface GanttRow {\r\n id: string\r\n label: string\r\n /** Texto pequeno à direita do rótulo (ex: \"12 dias\"). */\r\n sublabel?: string\r\n /** Mostra ⚠ com este texto no title. */\r\n warning?: string\r\n /** Cor dos blocos (CSS). Nas linhas-pai sem cor usa-se a cor de destaque. */\r\n color?: string\r\n /** Cor do texto do rótulo (CSS) — ex.: verde para tarefas concluídas. */\r\n labelColor?: string\r\n /** Dias com atividade (ISO yyyy-mm-dd). Nas linhas-pai, se omitido, agrega os dias dos filhos. */\r\n days?: string[]\r\n /** Dias marcados com losango (ex: conclusão). */\r\n markers?: string[]\r\n /** Cabeçalho de secção mostrado antes da linha quando muda (ex: \"Ramal 1\"). */\r\n section?: string\r\n children?: GanttRow[]\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface GanttLegendItem {\r\n label: string\r\n color: string\r\n kind?: 'block' | 'marker'\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface GanttChartProps {\r\n rows: GanttRow[]\r\n /** Início e fim do eixo (ISO). */\r\n start: string\r\n end: string\r\n /** Largura de cada dia em px (zoom). Default 14. */\r\n dayWidth?: number\r\n labelWidth?: number\r\n /** Título da coluna de rótulos. */\r\n labelHeader?: string\r\n legend?: GanttLegendItem[]\r\n /** Linhas-pai abertas (controlado). Sem isto o componente gere o estado. */\r\n expanded?: Set<string>\r\n onExpandedChange?: (ids: Set<string>) => void\r\n /** Conteúdo do tooltip de um dia. `parent` = linha-pai (dia agregado). */\r\n renderTooltip?: (info: { row: GanttRow; day: string; kind: 'day' | 'marker'; count?: number; parent?: GanttRow }) => React.ReactNode\r\n /** Nomes dos meses (i18n). Default PT. */\r\n monthNames?: string[]\r\n emptyText?: string\r\n className?: string\r\n}\r\n\r\nconst DIA_MS = 86_400_000\r\nconst MESES_PT = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']\r\nconst ACCENT = 'var(--gantt-accent, #1868B0)'\r\n\r\nconst toIdx = (iso: string, t0: number) => Math.round((Date.parse(`${iso}T00:00:00Z`) - t0) / DIA_MS)\r\n\r\n// ─── Componente ─────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport default function GanttChart({\r\n rows,\r\n start,\r\n end,\r\n dayWidth = 14,\r\n labelWidth = 300,\r\n labelHeader = '',\r\n legend,\r\n expanded: expandedProp,\r\n onExpandedChange,\r\n renderTooltip,\r\n monthNames = MESES_PT,\r\n emptyText = '—',\r\n className = '',\r\n}: GanttChartProps) {\r\n const [expandedLocal, setExpandedLocal] = useState<Set<string>>(new Set())\r\n const expanded = expandedProp ?? expandedLocal\r\n const setExpanded = (s: Set<string>) => (onExpandedChange ? onExpandedChange(s) : setExpandedLocal(s))\r\n const [tip, setTip] = useState<{ x: number; y: number; content: React.ReactNode } | null>(null)\r\n\r\n const t0 = Date.parse(`${start}T00:00:00Z`)\r\n const nDias = Math.max(1, toIdx(end, t0) + 1)\r\n const largura = nDias * dayWidth\r\n\r\n const dias = useMemo(\r\n () =>\r\n Array.from({ length: nDias }, (_, i) => {\r\n const d = new Date(t0 + i * DIA_MS)\r\n return { i, dia: d.getUTCDate(), mes: d.getUTCMonth(), ano: d.getUTCFullYear(), fds: d.getUTCDay() === 0 || d.getUTCDay() === 6 }\r\n }),\r\n [t0, nDias]\r\n )\r\n\r\n const toggle = (id: string) => {\r\n const s = new Set(expanded)\r\n if (s.has(id)) s.delete(id)\r\n else s.add(id)\r\n setExpanded(s)\r\n }\r\n\r\n const fundo = (\r\n <>\r\n {dias.filter((d) => d.fds).map((d) => (\r\n <div key={d.i} className=\"absolute inset-y-0 bg-slate-100\" style={{ left: d.i * dayWidth, width: dayWidth }} />\r\n ))}\r\n </>\r\n )\r\n\r\n const mostrarTip = (e: React.MouseEvent, content: React.ReactNode) => {\r\n if (!content) return\r\n setTip({ x: Math.min(e.clientX + 12, window.innerWidth - 340), y: e.clientY + 14, content })\r\n }\r\n\r\n const trilho = (blocos: { i: number; color: string; opacity?: number; tip?: React.ReactNode }[], marcas: { i: number; tip?: React.ReactNode }[], alto: boolean) => (\r\n <div className=\"relative shrink-0\" style={{ width: largura }}>\r\n {fundo}\r\n {blocos.map((b) => (\r\n <div\r\n key={`b${b.i}`}\r\n className={`absolute rounded-sm ${alto ? 'top-[5px] bottom-[5px]' : 'top-1 bottom-1'}`}\r\n style={{ left: b.i * dayWidth + 1, width: Math.max(dayWidth - 2, 2), background: b.color, opacity: b.opacity }}\r\n onMouseMove={(e) => mostrarTip(e, b.tip)}\r\n onMouseLeave={() => setTip(null)}\r\n />\r\n ))}\r\n {marcas.map((m) => (\r\n <div\r\n key={`m${m.i}`}\r\n className=\"absolute top-1/2 w-[9px] h-[9px] bg-slate-900 border-[1.5px] border-white\"\r\n style={{ left: m.i * dayWidth + dayWidth / 2, transform: 'translate(-50%,-50%) rotate(45deg)' }}\r\n onMouseMove={(e) => mostrarTip(e, m.tip)}\r\n onMouseLeave={() => setTip(null)}\r\n />\r\n ))}\r\n </div>\r\n )\r\n\r\n const linhas: React.ReactNode[] = []\r\n let secaoAtual: string | undefined\r\n for (const row of rows) {\r\n if (row.section && row.section !== secaoAtual) {\r\n secaoAtual = row.section\r\n linhas.push(\r\n <div key={`s-${row.section}-${row.id}`} className=\"flex border-b border-slate-200 bg-slate-50\">\r\n <div className=\"sticky left-0 z-[2] shrink-0 bg-slate-50 px-2 py-1 text-[11px] font-bold uppercase tracking-wide text-slate-500 border-r border-slate-200\" style={{ width: labelWidth }}>\r\n {row.section}\r\n </div>\r\n <div style={{ width: largura }} />\r\n </div>\r\n )\r\n }\r\n const filhos = row.children ?? []\r\n const aberto = expanded.has(row.id)\r\n let blocos: { i: number; color: string; opacity?: number; tip?: React.ReactNode }[]\r\n if (row.days) {\r\n blocos = row.days.map((d) => ({ i: toIdx(d, t0), color: row.color ?? ACCENT, tip: renderTooltip?.({ row, day: d, kind: 'day' }) }))\r\n } else {\r\n const cont = new Map<string, number>()\r\n for (const f of filhos) for (const d of f.days ?? []) cont.set(d, (cont.get(d) ?? 0) + 1)\r\n const max = Math.max(1, ...cont.values())\r\n blocos = [...cont.entries()].map(([d, n]) => ({\r\n i: toIdx(d, t0),\r\n color: row.color ?? ACCENT,\r\n opacity: 0.45 + (0.55 * n) / max,\r\n tip: renderTooltip?.({ row, day: d, kind: 'day', count: n }),\r\n }))\r\n }\r\n const marcas = (row.markers ?? []).map((d) => ({ i: toIdx(d, t0), tip: renderTooltip?.({ row, day: d, kind: 'marker' }) }))\r\n\r\n linhas.push(\r\n <div key={row.id} className=\"group flex min-h-[22px] border-b border-slate-200 hover:bg-blue-50/60\">\r\n <div\r\n className={`sticky left-0 z-[2] shrink-0 truncate border-r border-slate-200 bg-white px-2 py-[3px] group-hover:bg-blue-50 ${filhos.length ? 'cursor-pointer font-semibold' : ''}`}\r\n style={{ width: labelWidth, color: row.labelColor }}\r\n onClick={filhos.length ? () => toggle(row.id) : undefined}\r\n title={row.label}\r\n >\r\n {filhos.length > 0 && <span className=\"inline-block w-3.5 text-slate-400\">{aberto ? '▾' : '▸'}</span>}\r\n {row.label}\r\n {row.sublabel && <span className=\"ml-1 text-[11px] font-normal text-slate-400\">{row.sublabel}</span>}\r\n {row.warning && <span className=\"ml-1 text-orange-600\" title={row.warning}>⚠</span>}\r\n </div>\r\n {trilho(blocos, marcas, true)}\r\n </div>\r\n )\r\n\r\n if (aberto)\r\n for (const f of filhos) {\r\n const b = (f.days ?? []).map((d) => ({ i: toIdx(d, t0), color: f.color ?? ACCENT, tip: renderTooltip?.({ row: f, day: d, kind: 'day', parent: row }) }))\r\n const m = (f.markers ?? []).map((d) => ({ i: toIdx(d, t0), tip: renderTooltip?.({ row: f, day: d, kind: 'marker', parent: row }) }))\r\n linhas.push(\r\n <div key={`${row.id}::${f.id}`} className=\"group flex min-h-[22px] border-b border-slate-200 hover:bg-blue-50/60\">\r\n <div className=\"sticky left-0 z-[2] shrink-0 truncate border-r border-slate-200 bg-white py-[3px] pl-7 pr-2 text-slate-500 group-hover:bg-blue-50\" style={{ width: labelWidth, color: f.labelColor }} title={f.warning ? `${f.label} — ${f.warning}` : f.label}>\r\n {f.label}\r\n {f.sublabel && <span className=\"ml-1 text-[11px] text-slate-400\">{f.sublabel}</span>}\r\n {f.warning && <span className=\"ml-1 text-orange-600\">⚠</span>}\r\n </div>\r\n {trilho(b, m, false)}\r\n </div>\r\n )\r\n }\r\n }\r\n\r\n return (\r\n <div className={className}>\r\n {legend && legend.length > 0 && (\r\n <div className=\"mb-2 flex flex-wrap gap-3 text-xs text-slate-500\">\r\n {legend.map((l) => (\r\n <span key={l.label} className=\"inline-flex items-center gap-1\">\r\n <i className=\"inline-block h-2.5 w-2.5 rounded-sm\" style={{ background: l.color, transform: l.kind === 'marker' ? 'rotate(45deg) scale(.85)' : undefined }} />\r\n {l.label}\r\n </span>\r\n ))}\r\n </div>\r\n )}\r\n <div className=\"max-h-[calc(100vh-220px)] overflow-auto rounded-lg border border-slate-200 bg-white text-[13px]\">\r\n {/* w-max: o cabeçalho fixo cobre a largura toda do conteúdo (não só a parte visível) */}\r\n <div className=\"w-max min-w-full\">\r\n <div className=\"sticky top-0 z-[3] flex border-b border-slate-200 bg-white\">\r\n <div className=\"sticky left-0 z-[4] shrink-0 border-r border-slate-200 bg-white px-2 py-2 font-semibold\" style={{ width: labelWidth }}>\r\n {labelHeader}\r\n </div>\r\n <div className=\"relative shrink-0 bg-white\" style={{ width: largura, height: 36 }}>\r\n {dias.map((d) => (\r\n <Fragment key={d.i}>\r\n {(d.i === 0 || d.dia === 1) && (\r\n <div className=\"absolute top-0 h-[18px] whitespace-nowrap border-l border-slate-200 pl-1 font-semibold\" style={{ left: d.i * dayWidth }}>\r\n {monthNames[d.mes]} {d.ano}\r\n </div>\r\n )}\r\n {(dayWidth >= 14 || new Date(t0 + d.i * DIA_MS).getUTCDay() === 1) && (\r\n <div className=\"absolute top-[18px] h-4 text-[10px] text-slate-400\" style={{ left: d.i * dayWidth, width: dayWidth >= 14 ? dayWidth : dayWidth * 7, textAlign: dayWidth >= 14 ? 'center' : 'left' }}>\r\n {d.dia}\r\n </div>\r\n )}\r\n </Fragment>\r\n ))}\r\n </div>\r\n </div>\r\n {linhas.length ? linhas : <div className=\"p-6 text-center text-sm text-slate-400\">{emptyText}</div>}\r\n </div>\r\n </div>\r\n {tip && (\r\n <div className=\"pointer-events-none fixed z-50 max-w-[340px] rounded-md bg-slate-900 px-2 py-1.5 text-xs text-white shadow-lg\" style={{ left: tip.x, top: tip.y }}>\r\n {tip.content}\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Utilitário: ISO de um dia do eixo (para quem precisa de formatar tooltips).\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport const ganttDiaIso = (start: string, offset: number) => new Date(Date.parse(`${start}T00:00:00Z`) + offset * DIA_MS).toISOString().slice(0, 10)\r\n"],"names":["Fragment"],"mappings":";;AAuDA,MAAM,SAAS;AACf,MAAM,WAAW,CAAC,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AACpG,MAAM,SAAS;AAEf,MAAM,QAAQ,CAAC,KAAa,OAAe,KAAK,OAAO,KAAK,MAAM,GAAG,GAAG,YAAY,IAAI,MAAM,MAAM;AAKpG,SAAwB,WAAW;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,aAAa;AAAA,EACb,cAAc;AAAA,EACd;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,YAAY;AACd,GAAoB;AAClB,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAsB,oBAAI,KAAK;AACzE,QAAM,WAAW,gBAAgB;AACjC,QAAM,cAAc,CAAC,MAAoB,mBAAmB,iBAAiB,CAAC,IAAI,iBAAiB,CAAC;AACpG,QAAM,CAAC,KAAK,MAAM,IAAI,SAAoE,IAAI;AAE9F,QAAM,KAAK,KAAK,MAAM,GAAG,KAAK,YAAY;AAC1C,QAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,KAAK,EAAE,IAAI,CAAC;AAC5C,QAAM,UAAU,QAAQ;AAExB,QAAM,OAAO;AAAA,IACX,MACE,MAAM,KAAK,EAAE,QAAQ,SAAS,CAAC,GAAG,MAAM;AACtC,YAAM,IAAI,IAAI,KAAK,KAAK,IAAI,MAAM;AAClC,aAAO,EAAE,GAAG,KAAK,EAAE,cAAc,KAAK,EAAE,YAAA,GAAe,KAAK,EAAE,eAAA,GAAkB,KAAK,EAAE,UAAA,MAAgB,KAAK,EAAE,UAAA,MAAgB,EAAA;AAAA,IAChI,CAAC;AAAA,IACH,CAAC,IAAI,KAAK;AAAA,EAAA;AAGZ,QAAM,SAAS,CAAC,OAAe;AAC7B,UAAM,IAAI,IAAI,IAAI,QAAQ;AAC1B,QAAI,EAAE,IAAI,EAAE,EAAG,GAAE,OAAO,EAAE;AAAA,QACrB,GAAE,IAAI,EAAE;AACb,gBAAY,CAAC;AAAA,EACf;AAEA,QAAM,QACJ,oBAAA,UAAA,EACG,UAAA,KAAK,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,MAC9B,oBAAC,OAAA,EAAc,WAAU,mCAAkC,OAAO,EAAE,MAAM,EAAE,IAAI,UAAU,OAAO,SAAA,EAAS,GAAhG,EAAE,CAAiG,CAC9G,EAAA,CACH;AAGF,QAAM,aAAa,CAAC,GAAqB,YAA6B;AACpE,QAAI,CAAC,QAAS;AACd,WAAO,EAAE,GAAG,KAAK,IAAI,EAAE,UAAU,IAAI,OAAO,aAAa,GAAG,GAAG,GAAG,EAAE,UAAU,IAAI,SAAS;AAAA,EAC7F;AAEA,QAAM,SAAS,CAAC,QAAiF,QAAgD,SAC/I,qBAAC,OAAA,EAAI,WAAU,qBAAoB,OAAO,EAAE,OAAO,WAChD,UAAA;AAAA,IAAA;AAAA,IACA,OAAO,IAAI,CAAC,MACX;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAW,uBAAuB,OAAO,2BAA2B,gBAAgB;AAAA,QACpF,OAAO,EAAE,MAAM,EAAE,IAAI,WAAW,GAAG,OAAO,KAAK,IAAI,WAAW,GAAG,CAAC,GAAG,YAAY,EAAE,OAAO,SAAS,EAAE,QAAA;AAAA,QACrG,aAAa,CAAC,MAAM,WAAW,GAAG,EAAE,GAAG;AAAA,QACvC,cAAc,MAAM,OAAO,IAAI;AAAA,MAAA;AAAA,MAJ1B,IAAI,EAAE,CAAC;AAAA,IAAA,CAMf;AAAA,IACA,OAAO,IAAI,CAAC,MACX;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,WAAU;AAAA,QACV,OAAO,EAAE,MAAM,EAAE,IAAI,WAAW,WAAW,GAAG,WAAW,qCAAA;AAAA,QACzD,aAAa,CAAC,MAAM,WAAW,GAAG,EAAE,GAAG;AAAA,QACvC,cAAc,MAAM,OAAO,IAAI;AAAA,MAAA;AAAA,MAJ1B,IAAI,EAAE,CAAC;AAAA,IAAA,CAMf;AAAA,EAAA,GACH;AAGF,QAAM,SAA4B,CAAA;AAClC,MAAI;AACJ,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,WAAW,IAAI,YAAY,YAAY;AAC7C,mBAAa,IAAI;AACjB,aAAO;AAAA,QACL,qBAAC,OAAA,EAAuC,WAAU,8CAChD,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAU,6IAA4I,OAAO,EAAE,OAAO,WAAA,GACxK,UAAA,IAAI,QAAA,CACP;AAAA,8BACC,OAAA,EAAI,OAAO,EAAE,OAAO,UAAQ,CAAG;AAAA,QAAA,EAAA,GAJxB,KAAK,IAAI,OAAO,IAAI,IAAI,EAAE,EAKpC;AAAA,MAAA;AAAA,IAEJ;AACA,UAAM,SAAS,IAAI,YAAY,CAAA;AAC/B,UAAM,SAAS,SAAS,IAAI,IAAI,EAAE;AAClC,QAAI;AACJ,QAAI,IAAI,MAAM;AACZ,eAAS,IAAI,KAAK,IAAI,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,EAAE,GAAG,OAAO,IAAI,SAAS,QAAQ,KAAK,gBAAgB,EAAE,KAAK,KAAK,GAAG,MAAM,MAAA,CAAO,EAAA,EAAI;AAAA,IACpI,OAAO;AACL,YAAM,2BAAW,IAAA;AACjB,iBAAW,KAAK,OAAQ,YAAW,KAAK,EAAE,QAAQ,CAAA,EAAI,MAAK,IAAI,IAAI,KAAK,IAAI,CAAC,KAAK,KAAK,CAAC;AACxF,YAAM,MAAM,KAAK,IAAI,GAAG,GAAG,KAAK,QAAQ;AACxC,eAAS,CAAC,GAAG,KAAK,QAAA,CAAS,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO;AAAA,QAC5C,GAAG,MAAM,GAAG,EAAE;AAAA,QACd,OAAO,IAAI,SAAS;AAAA,QACpB,SAAS,OAAQ,OAAO,IAAK;AAAA,QAC7B,KAAK,gBAAgB,EAAE,KAAK,KAAK,GAAG,MAAM,OAAO,OAAO,EAAA,CAAG;AAAA,MAAA,EAC3D;AAAA,IACJ;AACA,UAAM,UAAU,IAAI,WAAW,CAAA,GAAI,IAAI,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,EAAE,GAAG,KAAK,gBAAgB,EAAE,KAAK,KAAK,GAAG,MAAM,UAAU,EAAA,EAAI;AAE1H,WAAO;AAAA,MACL,qBAAC,OAAA,EAAiB,WAAU,yEAC1B,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAW,iHAAiH,OAAO,SAAS,iCAAiC,EAAE;AAAA,YAC/K,OAAO,EAAE,OAAO,YAAY,OAAO,IAAI,WAAA;AAAA,YACvC,SAAS,OAAO,SAAS,MAAM,OAAO,IAAI,EAAE,IAAI;AAAA,YAChD,OAAO,IAAI;AAAA,YAEV,UAAA;AAAA,cAAA,OAAO,SAAS,KAAK,oBAAC,QAAA,EAAK,WAAU,qCAAqC,UAAA,SAAS,MAAM,IAAA,CAAI;AAAA,cAC7F,IAAI;AAAA,cACJ,IAAI,YAAY,oBAAC,UAAK,WAAU,+CAA+C,cAAI,UAAS;AAAA,cAC5F,IAAI,WAAW,oBAAC,QAAA,EAAK,WAAU,wBAAuB,OAAO,IAAI,SAAS,UAAA,IAAA,CAAC;AAAA,YAAA;AAAA,UAAA;AAAA,QAAA;AAAA,QAE7E,OAAO,QAAQ,QAAQ,IAAI;AAAA,MAAA,EAAA,GAZpB,IAAI,EAad;AAAA,IAAA;AAGF,QAAI;AACF,iBAAW,KAAK,QAAQ;AACtB,cAAM,KAAK,EAAE,QAAQ,CAAA,GAAI,IAAI,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,EAAE,GAAG,OAAO,EAAE,SAAS,QAAQ,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,OAAO,QAAQ,IAAA,CAAK,IAAI;AACvJ,cAAM,KAAK,EAAE,WAAW,CAAA,GAAI,IAAI,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,EAAE,GAAG,KAAK,gBAAgB,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,UAAU,QAAQ,IAAA,CAAK,EAAA,EAAI;AACnI,eAAO;AAAA,UACL,qBAAC,OAAA,EAA+B,WAAU,yEACxC,UAAA;AAAA,YAAA,qBAAC,OAAA,EAAI,WAAU,qIAAoI,OAAO,EAAE,OAAO,YAAY,OAAO,EAAE,WAAA,GAAc,OAAO,EAAE,UAAU,GAAG,EAAE,KAAK,MAAM,EAAE,OAAO,KAAK,EAAE,OACtP,UAAA;AAAA,cAAA,EAAE;AAAA,cACF,EAAE,YAAY,oBAAC,UAAK,WAAU,mCAAmC,YAAE,UAAS;AAAA,cAC5E,EAAE,WAAW,oBAAC,QAAA,EAAK,WAAU,wBAAuB,UAAA,IAAA,CAAC;AAAA,YAAA,GACxD;AAAA,YACC,OAAO,GAAG,GAAG,KAAK;AAAA,UAAA,EAAA,GANX,GAAG,IAAI,EAAE,KAAK,EAAE,EAAE,EAO5B;AAAA,QAAA;AAAA,MAEJ;AAAA,EACJ;AAEA,SACE,qBAAC,SAAI,WACF,UAAA;AAAA,IAAA,UAAU,OAAO,SAAS,KACzB,oBAAC,SAAI,WAAU,oDACZ,UAAA,OAAO,IAAI,CAAC,MACX,qBAAC,QAAA,EAAmB,WAAU,kCAC5B,UAAA;AAAA,MAAA,oBAAC,KAAA,EAAE,WAAU,uCAAsC,OAAO,EAAE,YAAY,EAAE,OAAO,WAAW,EAAE,SAAS,WAAW,6BAA6B,UAAa;AAAA,MAC3J,EAAE;AAAA,IAAA,EAAA,GAFM,EAAE,KAGb,CACD,GACH;AAAA,wBAED,OAAA,EAAI,WAAU,mGAEb,UAAA,qBAAC,OAAA,EAAI,WAAU,oBACf,UAAA;AAAA,MAAA,qBAAC,OAAA,EAAI,WAAU,8DACb,UAAA;AAAA,QAAA,oBAAC,OAAA,EAAI,WAAU,2FAA0F,OAAO,EAAE,OAAO,WAAA,GACtH,UAAA,YAAA,CACH;AAAA,4BACC,OAAA,EAAI,WAAU,8BAA6B,OAAO,EAAE,OAAO,SAAS,QAAQ,GAAA,GAC1E,UAAA,KAAK,IAAI,CAAC,MACT,qBAACA,YAAA,EACG,UAAA;AAAA,WAAA,EAAE,MAAM,KAAK,EAAE,QAAQ,MACvB,qBAAC,OAAA,EAAI,WAAU,0FAAyF,OAAO,EAAE,MAAM,EAAE,IAAI,YAC1H,UAAA;AAAA,YAAA,WAAW,EAAE,GAAG;AAAA,YAAE;AAAA,YAAE,EAAE;AAAA,UAAA,GACzB;AAAA,WAEA,YAAY,MAAM,IAAI,KAAK,KAAK,EAAE,IAAI,MAAM,EAAE,gBAAgB,MAC9D,oBAAC,OAAA,EAAI,WAAU,sDAAqD,OAAO,EAAE,MAAM,EAAE,IAAI,UAAU,OAAO,YAAY,KAAK,WAAW,WAAW,GAAG,WAAW,YAAY,KAAK,WAAW,OAAA,GACxL,YAAE,IAAA,CACL;AAAA,QAAA,KATW,EAAE,CAWjB,CACD,EAAA,CACH;AAAA,MAAA,GACF;AAAA,MACC,OAAO,SAAS,6BAAU,OAAA,EAAI,WAAU,0CAA0C,UAAA,UAAA,CAAU;AAAA,IAAA,EAAA,CAC7F,EAAA,CACF;AAAA,IACC,OACC,oBAAC,OAAA,EAAI,WAAU,iHAAgH,OAAO,EAAE,MAAM,IAAI,GAAG,KAAK,IAAI,EAAA,GAC3J,cAAI,QAAA,CACP;AAAA,EAAA,GAEJ;AAEJ;AAMO,MAAM,cAAc,CAAC,OAAe,WAAmB,IAAI,KAAK,KAAK,MAAM,GAAG,KAAK,YAAY,IAAI,SAAS,MAAM,EAAE,cAAc,MAAM,GAAG,EAAE;"}
1
+ {"version":3,"file":"GanttChart.js","sources":["../../../../../../src/legacy/components/organisms/GanttChart/GanttChart.tsx"],"sourcesContent":["import React, { Fragment, useMemo, useState } from 'react'\r\n\r\n// ─── Tipos ──────────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface GanttRow {\r\n id: string\r\n label: string\r\n /** Texto pequeno à direita do rótulo (ex: \"12 dias\"). */\r\n sublabel?: string\r\n /** Mostra ⚠ com este texto no title. */\r\n warning?: string\r\n /** Cor dos blocos (CSS). Nas linhas-pai sem cor usa-se a cor de destaque. */\r\n color?: string\r\n /** Cor do texto do rótulo (CSS) — ex.: verde para tarefas concluídas. */\r\n labelColor?: string\r\n /** Dias com atividade (ISO yyyy-mm-dd). Nas linhas-pai, se omitido, agrega os dias dos filhos. */\r\n days?: string[]\r\n /** Dias marcados com losango (ex: conclusão). */\r\n markers?: string[]\r\n /** Cabeçalho de secção mostrado antes da linha quando muda (ex: \"Ramal 1\"). */\r\n section?: string\r\n children?: GanttRow[]\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface GanttLegendItem {\r\n label: string\r\n color: string\r\n kind?: 'block' | 'marker'\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface GanttClickInfo {\r\n row: GanttRow\r\n /** Dia clicado (ISO yyyy-mm-dd) — só nos losangos. */\r\n day?: string\r\n /** Linha-pai quando `row` é uma linha-filho. */\r\n parent?: GanttRow\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface GanttChartProps {\r\n rows: GanttRow[]\r\n /** Início e fim do eixo (ISO). */\r\n start: string\r\n end: string\r\n /** Largura de cada dia em px (zoom). Default 14. */\r\n dayWidth?: number\r\n labelWidth?: number\r\n /** Título da coluna de rótulos. */\r\n labelHeader?: string\r\n legend?: GanttLegendItem[]\r\n /** Linhas-pai abertas (controlado). Sem isto o componente gere o estado. */\r\n expanded?: Set<string>\r\n onExpandedChange?: (ids: Set<string>) => void\r\n /** Conteúdo do tooltip de um dia. `parent` = linha-pai (dia agregado). */\r\n renderTooltip?: (info: { row: GanttRow; day: string; kind: 'day' | 'marker'; count?: number; parent?: GanttRow }) => React.ReactNode\r\n /** Clique num losango (marker). Torna-o clicável. */\r\n onMarkerClick?: (info: GanttClickInfo & { day: string }) => void\r\n /** Clique no rótulo de uma linha-filho (as linhas-pai continuam a expandir/recolher). */\r\n onRowClick?: (info: GanttClickInfo) => void\r\n /** Nomes dos meses (i18n). Default PT. */\r\n monthNames?: string[]\r\n emptyText?: string\r\n className?: string\r\n}\r\n\r\nconst DIA_MS = 86_400_000\r\nconst MESES_PT = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez']\r\nconst ACCENT = 'var(--gantt-accent, #1868B0)'\r\n\r\nconst toIdx = (iso: string, t0: number) => Math.round((Date.parse(`${iso}T00:00:00Z`) - t0) / DIA_MS)\r\n\r\n// ─── Componente ─────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport default function GanttChart({\r\n rows,\r\n start,\r\n end,\r\n dayWidth = 14,\r\n labelWidth = 300,\r\n labelHeader = '',\r\n legend,\r\n expanded: expandedProp,\r\n onExpandedChange,\r\n renderTooltip,\r\n onMarkerClick,\r\n onRowClick,\r\n monthNames = MESES_PT,\r\n emptyText = '—',\r\n className = '',\r\n}: GanttChartProps) {\r\n const [expandedLocal, setExpandedLocal] = useState<Set<string>>(new Set())\r\n const expanded = expandedProp ?? expandedLocal\r\n const setExpanded = (s: Set<string>) => (onExpandedChange ? onExpandedChange(s) : setExpandedLocal(s))\r\n const [tip, setTip] = useState<{ x: number; y: number; content: React.ReactNode } | null>(null)\r\n\r\n const t0 = Date.parse(`${start}T00:00:00Z`)\r\n const nDias = Math.max(1, toIdx(end, t0) + 1)\r\n const largura = nDias * dayWidth\r\n\r\n const dias = useMemo(\r\n () =>\r\n Array.from({ length: nDias }, (_, i) => {\r\n const d = new Date(t0 + i * DIA_MS)\r\n return { i, dia: d.getUTCDate(), mes: d.getUTCMonth(), ano: d.getUTCFullYear(), fds: d.getUTCDay() === 0 || d.getUTCDay() === 6 }\r\n }),\r\n [t0, nDias]\r\n )\r\n\r\n const toggle = (id: string) => {\r\n const s = new Set(expanded)\r\n if (s.has(id)) s.delete(id)\r\n else s.add(id)\r\n setExpanded(s)\r\n }\r\n\r\n const fundo = (\r\n <>\r\n {dias.filter((d) => d.fds).map((d) => (\r\n <div key={d.i} className=\"absolute inset-y-0 bg-slate-100\" style={{ left: d.i * dayWidth, width: dayWidth }} />\r\n ))}\r\n </>\r\n )\r\n\r\n const mostrarTip = (e: React.MouseEvent, content: React.ReactNode) => {\r\n if (!content) return\r\n setTip({ x: Math.min(e.clientX + 12, window.innerWidth - 340), y: e.clientY + 14, content })\r\n }\r\n\r\n const trilho = (blocos: { i: number; color: string; opacity?: number; tip?: React.ReactNode }[], marcas: { i: number; tip?: React.ReactNode; onClick?: () => void }[], alto: boolean) => (\r\n <div className=\"relative shrink-0\" style={{ width: largura }}>\r\n {fundo}\r\n {blocos.map((b) => (\r\n <div\r\n key={`b${b.i}`}\r\n className={`absolute rounded-sm ${alto ? 'top-[5px] bottom-[5px]' : 'top-1 bottom-1'}`}\r\n style={{ left: b.i * dayWidth + 1, width: Math.max(dayWidth - 2, 2), background: b.color, opacity: b.opacity }}\r\n onMouseMove={(e) => mostrarTip(e, b.tip)}\r\n onMouseLeave={() => setTip(null)}\r\n />\r\n ))}\r\n {marcas.map((m) => (\r\n <div\r\n key={`m${m.i}`}\r\n className={`absolute top-1/2 w-[9px] h-[9px] bg-slate-900 border-[1.5px] border-white ${m.onClick ? 'cursor-pointer' : ''}`}\r\n style={{ left: m.i * dayWidth + dayWidth / 2, transform: 'translate(-50%,-50%) rotate(45deg)' }}\r\n onClick={m.onClick}\r\n onMouseMove={(e) => mostrarTip(e, m.tip)}\r\n onMouseLeave={() => setTip(null)}\r\n />\r\n ))}\r\n </div>\r\n )\r\n\r\n const linhas: React.ReactNode[] = []\r\n let secaoAtual: string | undefined\r\n for (const row of rows) {\r\n if (row.section && row.section !== secaoAtual) {\r\n secaoAtual = row.section\r\n linhas.push(\r\n <div key={`s-${row.section}-${row.id}`} className=\"flex border-b border-slate-200 bg-slate-50\">\r\n <div className=\"sticky left-0 z-[2] shrink-0 bg-slate-50 px-2 py-1 text-[11px] font-bold uppercase tracking-wide text-slate-500 border-r border-slate-200\" style={{ width: labelWidth }}>\r\n {row.section}\r\n </div>\r\n <div style={{ width: largura }} />\r\n </div>\r\n )\r\n }\r\n const filhos = row.children ?? []\r\n const aberto = expanded.has(row.id)\r\n let blocos: { i: number; color: string; opacity?: number; tip?: React.ReactNode }[]\r\n if (row.days) {\r\n blocos = row.days.map((d) => ({ i: toIdx(d, t0), color: row.color ?? ACCENT, tip: renderTooltip?.({ row, day: d, kind: 'day' }) }))\r\n } else {\r\n const cont = new Map<string, number>()\r\n for (const f of filhos) for (const d of f.days ?? []) cont.set(d, (cont.get(d) ?? 0) + 1)\r\n const max = Math.max(1, ...cont.values())\r\n blocos = [...cont.entries()].map(([d, n]) => ({\r\n i: toIdx(d, t0),\r\n color: row.color ?? ACCENT,\r\n opacity: 0.45 + (0.55 * n) / max,\r\n tip: renderTooltip?.({ row, day: d, kind: 'day', count: n }),\r\n }))\r\n }\r\n const marcas = (row.markers ?? []).map((d) => ({ i: toIdx(d, t0), tip: renderTooltip?.({ row, day: d, kind: 'marker' }), onClick: onMarkerClick && (() => onMarkerClick({ row, day: d })) }))\r\n\r\n linhas.push(\r\n <div key={row.id} className=\"group flex min-h-[22px] border-b border-slate-200 hover:bg-blue-50/60\">\r\n <div\r\n className={`sticky left-0 z-[2] shrink-0 truncate border-r border-slate-200 bg-white px-2 py-[3px] group-hover:bg-blue-50 ${filhos.length ? 'cursor-pointer font-semibold' : ''}`}\r\n style={{ width: labelWidth, color: row.labelColor }}\r\n onClick={filhos.length ? () => toggle(row.id) : undefined}\r\n title={row.label}\r\n >\r\n {filhos.length > 0 && <span className=\"inline-block w-3.5 text-slate-400\">{aberto ? '▾' : '▸'}</span>}\r\n {row.label}\r\n {row.sublabel && <span className=\"ml-1 text-[11px] font-normal text-slate-400\">{row.sublabel}</span>}\r\n {row.warning && <span className=\"ml-1 text-orange-600\" title={row.warning}>⚠</span>}\r\n </div>\r\n {trilho(blocos, marcas, true)}\r\n </div>\r\n )\r\n\r\n if (aberto)\r\n for (const f of filhos) {\r\n const b = (f.days ?? []).map((d) => ({ i: toIdx(d, t0), color: f.color ?? ACCENT, tip: renderTooltip?.({ row: f, day: d, kind: 'day', parent: row }) }))\r\n const m = (f.markers ?? []).map((d) => ({ i: toIdx(d, t0), tip: renderTooltip?.({ row: f, day: d, kind: 'marker', parent: row }), onClick: onMarkerClick && (() => onMarkerClick({ row: f, day: d, parent: row })) }))\r\n linhas.push(\r\n <div key={`${row.id}::${f.id}`} className=\"group flex min-h-[22px] border-b border-slate-200 hover:bg-blue-50/60\">\r\n <div className={`sticky left-0 z-[2] shrink-0 truncate border-r border-slate-200 bg-white py-[3px] pl-7 pr-2 text-slate-500 group-hover:bg-blue-50 ${onRowClick ? 'cursor-pointer' : ''}`} style={{ width: labelWidth, color: f.labelColor }} onClick={onRowClick ? () => onRowClick({ row: f, parent: row }) : undefined} title={f.warning ? `${f.label} — ${f.warning}` : f.label}>\r\n {f.label}\r\n {f.sublabel && <span className=\"ml-1 text-[11px] text-slate-400\">{f.sublabel}</span>}\r\n {f.warning && <span className=\"ml-1 text-orange-600\">⚠</span>}\r\n </div>\r\n {trilho(b, m, false)}\r\n </div>\r\n )\r\n }\r\n }\r\n\r\n return (\r\n <div className={className}>\r\n {legend && legend.length > 0 && (\r\n <div className=\"mb-2 flex flex-wrap gap-3 text-xs text-slate-500\">\r\n {legend.map((l) => (\r\n <span key={l.label} className=\"inline-flex items-center gap-1\">\r\n <i className=\"inline-block h-2.5 w-2.5 rounded-sm\" style={{ background: l.color, transform: l.kind === 'marker' ? 'rotate(45deg) scale(.85)' : undefined }} />\r\n {l.label}\r\n </span>\r\n ))}\r\n </div>\r\n )}\r\n <div className=\"max-h-[calc(100vh-220px)] overflow-auto rounded-lg border border-slate-200 bg-white text-[13px]\">\r\n {/* w-max: o cabeçalho fixo cobre a largura toda do conteúdo (não só a parte visível) */}\r\n <div className=\"w-max min-w-full\">\r\n <div className=\"sticky top-0 z-[3] flex border-b border-slate-200 bg-white\">\r\n <div className=\"sticky left-0 z-[4] shrink-0 border-r border-slate-200 bg-white px-2 py-2 font-semibold\" style={{ width: labelWidth }}>\r\n {labelHeader}\r\n </div>\r\n <div className=\"relative shrink-0 bg-white\" style={{ width: largura, height: 36 }}>\r\n {dias.map((d) => (\r\n <Fragment key={d.i}>\r\n {(d.i === 0 || d.dia === 1) && (\r\n <div className=\"absolute top-0 h-[18px] whitespace-nowrap border-l border-slate-200 pl-1 font-semibold\" style={{ left: d.i * dayWidth }}>\r\n {monthNames[d.mes]} {d.ano}\r\n </div>\r\n )}\r\n {(dayWidth >= 14 || new Date(t0 + d.i * DIA_MS).getUTCDay() === 1) && (\r\n <div className=\"absolute top-[18px] h-4 text-[10px] text-slate-400\" style={{ left: d.i * dayWidth, width: dayWidth >= 14 ? dayWidth : dayWidth * 7, textAlign: dayWidth >= 14 ? 'center' : 'left' }}>\r\n {d.dia}\r\n </div>\r\n )}\r\n </Fragment>\r\n ))}\r\n </div>\r\n </div>\r\n {linhas.length ? linhas : <div className=\"p-6 text-center text-sm text-slate-400\">{emptyText}</div>}\r\n </div>\r\n </div>\r\n {tip && (\r\n <div className=\"pointer-events-none fixed z-50 max-w-[340px] rounded-md bg-slate-900 px-2 py-1.5 text-xs text-white shadow-lg\" style={{ left: tip.x, top: tip.y }}>\r\n {tip.content}\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\n/**\r\n * Utilitário: ISO de um dia do eixo (para quem precisa de formatar tooltips).\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport const ganttDiaIso = (start: string, offset: number) => new Date(Date.parse(`${start}T00:00:00Z`) + offset * DIA_MS).toISOString().slice(0, 10)\r\n"],"names":["Fragment"],"mappings":"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"}
@@ -14,11 +14,14 @@ function useFloatingPanel(align = "left") {
14
14
  }
15
15
  const r = triggerRef.current.getBoundingClientRect();
16
16
  const height = panelRef.current?.offsetHeight ?? 240;
17
- const horizontal = align === "right" ? { right: window.innerWidth - r.right } : { left: r.left };
17
+ const vw = window.innerWidth;
18
+ const width = Math.min(Math.max(panelRef.current?.offsetWidth ?? 0, r.width), vw - 16);
19
+ const horizontal = align === "right" ? { right: Math.min(Math.max(8, vw - r.right), vw - 8 - width) } : { left: Math.min(Math.max(8, r.left), vw - 8 - width) };
18
20
  const fitsBelow = r.bottom + 4 + height <= window.innerHeight - 8;
19
21
  setPos({
20
22
  ...horizontal,
21
- minWidth: r.width,
23
+ minWidth: Math.min(r.width, vw - 16),
24
+ maxWidth: vw - 16,
22
25
  ...fitsBelow || r.top < height ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }
23
26
  });
24
27
  }, [open, align]);
@@ -1 +1 @@
1
- {"version":3,"file":"useFloatingPanel.js","sources":["../../../../../src/v1/capabilities/overlay/useFloatingPanel.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react'\r\n\r\n/**\r\n * Aspeto comum das listas suspensas (SelectInput, MenuAcoes): cartão arredondado com sombra, nos dois temas.\r\n * Um só sítio para ficarem iguais.\r\n */\r\nexport const PANEL_CLASS =\r\n 'fixed z-[60] min-w-[10rem] max-h-72 overflow-auto rounded-xl border border-slate-200 bg-white py-1 text-sm shadow-lg dark:border-slate-700 dark:bg-slate-900'\r\n\r\n/** Item de uma lista suspensa; `active` = o que o teclado tem selecionado. */\r\nexport const itemClass = (active: boolean, danger = false) =>\r\n `w-full flex items-center gap-2.5 px-3 py-2 text-left cursor-pointer select-none ${\r\n danger ? 'text-red-600 dark:text-red-300' : 'text-slate-700 dark:text-slate-200'\r\n } ${active ? 'bg-slate-100 dark:bg-slate-800' : 'bg-transparent hover:bg-slate-50 dark:hover:bg-slate-800'}`\r\n\r\n/**\r\n * Painel flutuante preso a um botão: posição fixa (não é cortado por tabelas ou janelas com scroll), abre para\r\n * cima quando não cabe por baixo, fecha ao clicar fora, ao fazer scroll fora dele ou ao redimensionar.\r\n */\r\nexport function useFloatingPanel(align: 'left' | 'right' = 'left') {\r\n const [open, setOpen] = useState(false)\r\n const triggerRef = useRef<HTMLButtonElement>(null)\r\n const panelRef = useRef<HTMLDivElement>(null)\r\n const [pos, setPos] = useState<CSSProperties | null>(null)\r\n\r\n const close = useCallback(() => setOpen(false), [])\r\n\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null)\r\n return\r\n }\r\n const r = triggerRef.current.getBoundingClientRect()\r\n const height = panelRef.current?.offsetHeight ?? 240\r\n const horizontal = align === 'right' ? { right: window.innerWidth - r.right } : { left: r.left }\r\n const fitsBelow = r.bottom + 4 + height <= window.innerHeight - 8\r\n setPos({\r\n ...horizontal,\r\n minWidth: r.width,\r\n ...(fitsBelow || r.top < height ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }),\r\n })\r\n }, [open, align])\r\n\r\n useEffect(() => {\r\n if (!open) return\r\n const click = (e: MouseEvent) => {\r\n const t = e.target as Node\r\n if (!triggerRef.current?.contains(t) && !panelRef.current?.contains(t)) setOpen(false)\r\n }\r\n const scroll = (e: Event) => {\r\n if (!panelRef.current?.contains(e.target as Node)) setOpen(false)\r\n }\r\n const resize = () => setOpen(false)\r\n document.addEventListener('mousedown', click)\r\n window.addEventListener('scroll', scroll, true)\r\n window.addEventListener('resize', resize)\r\n return () => {\r\n document.removeEventListener('mousedown', click)\r\n window.removeEventListener('scroll', scroll, true)\r\n window.removeEventListener('resize', resize)\r\n }\r\n }, [open])\r\n\r\n // Antes de medir, o painel desenha-se invisível (para se saber a altura).\r\n const style: CSSProperties = pos ?? { top: 0, left: 0, visibility: 'hidden' }\r\n return { open, setOpen, close, triggerRef, panelRef, style }\r\n}\r\n"],"names":[],"mappings":";AAMO,MAAM,cACX;AAGK,MAAM,YAAY,CAAC,QAAiB,SAAS,UAClD,mFACE,SAAS,mCAAmC,oCAC9C,IAAI,SAAS,mCAAmC,0DAA0D;AAMrG,SAAS,iBAAiB,QAA0B,QAAQ;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,KAAK,MAAM,IAAI,SAA+B,IAAI;AAEzD,QAAM,QAAQ,YAAY,MAAM,QAAQ,KAAK,GAAG,CAAA,CAAE;AAElD,kBAAgB,MAAM;AACpB,QAAI,CAAC,QAAQ,CAAC,WAAW,SAAS;AAChC,aAAO,IAAI;AACX;AAAA,IACF;AACA,UAAM,IAAI,WAAW,QAAQ,sBAAA;AAC7B,UAAM,SAAS,SAAS,SAAS,gBAAgB;AACjD,UAAM,aAAa,UAAU,UAAU,EAAE,OAAO,OAAO,aAAa,EAAE,MAAA,IAAU,EAAE,MAAM,EAAE,KAAA;AAC1F,UAAM,YAAY,EAAE,SAAS,IAAI,UAAU,OAAO,cAAc;AAChE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU,EAAE;AAAA,MACZ,GAAI,aAAa,EAAE,MAAM,SAAS,EAAE,KAAK,EAAE,SAAS,EAAA,IAAM,EAAE,QAAQ,OAAO,cAAc,EAAE,MAAM,EAAA;AAAA,IAAE,CACpG;AAAA,EACH,GAAG,CAAC,MAAM,KAAK,CAAC;AAEhB,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,MAAkB;AAC/B,YAAM,IAAI,EAAE;AACZ,UAAI,CAAC,WAAW,SAAS,SAAS,CAAC,KAAK,CAAC,SAAS,SAAS,SAAS,CAAC,WAAW,KAAK;AAAA,IACvF;AACA,UAAM,SAAS,CAAC,MAAa;AAC3B,UAAI,CAAC,SAAS,SAAS,SAAS,EAAE,MAAc,WAAW,KAAK;AAAA,IAClE;AACA,UAAM,SAAS,MAAM,QAAQ,KAAK;AAClC,aAAS,iBAAiB,aAAa,KAAK;AAC5C,WAAO,iBAAiB,UAAU,QAAQ,IAAI;AAC9C,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,KAAK;AAC/C,aAAO,oBAAoB,UAAU,QAAQ,IAAI;AACjD,aAAO,oBAAoB,UAAU,MAAM;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAGT,QAAM,QAAuB,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,SAAA;AACnE,SAAO,EAAE,MAAM,SAAS,OAAO,YAAY,UAAU,MAAA;AACvD;"}
1
+ {"version":3,"file":"useFloatingPanel.js","sources":["../../../../../src/v1/capabilities/overlay/useFloatingPanel.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from 'react'\r\n\r\n/**\r\n * Aspeto comum das listas suspensas (SelectInput, MenuAcoes): cartão arredondado com sombra, nos dois temas.\r\n * Um só sítio para ficarem iguais.\r\n */\r\nexport const PANEL_CLASS =\r\n 'fixed z-[60] min-w-[10rem] max-h-72 overflow-auto rounded-xl border border-slate-200 bg-white py-1 text-sm shadow-lg dark:border-slate-700 dark:bg-slate-900'\r\n\r\n/** Item de uma lista suspensa; `active` = o que o teclado tem selecionado. */\r\nexport const itemClass = (active: boolean, danger = false) =>\r\n `w-full flex items-center gap-2.5 px-3 py-2 text-left cursor-pointer select-none ${\r\n danger ? 'text-red-600 dark:text-red-300' : 'text-slate-700 dark:text-slate-200'\r\n } ${active ? 'bg-slate-100 dark:bg-slate-800' : 'bg-transparent hover:bg-slate-50 dark:hover:bg-slate-800'}`\r\n\r\n/**\r\n * Painel flutuante preso a um botão: posição fixa (não é cortado por tabelas ou janelas com scroll), abre para\r\n * cima quando não cabe por baixo, fecha ao clicar fora, ao fazer scroll fora dele ou ao redimensionar.\r\n */\r\nexport function useFloatingPanel(align: 'left' | 'right' = 'left') {\r\n const [open, setOpen] = useState(false)\r\n const triggerRef = useRef<HTMLButtonElement>(null)\r\n const panelRef = useRef<HTMLDivElement>(null)\r\n const [pos, setPos] = useState<CSSProperties | null>(null)\r\n\r\n const close = useCallback(() => setOpen(false), [])\r\n\r\n useLayoutEffect(() => {\r\n if (!open || !triggerRef.current) {\r\n setPos(null)\r\n return\r\n }\r\n const r = triggerRef.current.getBoundingClientRect()\r\n const height = panelRef.current?.offsetHeight ?? 240\r\n // Nunca sai do ecrã para o lado (ex: um select à direita, no telemóvel, com opções mais largas do que ele):\r\n // encosta-se a 8px da margem e, se for mais largo do que o ecrã, fica com a largura dele.\r\n const vw = window.innerWidth\r\n const width = Math.min(Math.max(panelRef.current?.offsetWidth ?? 0, r.width), vw - 16)\r\n const horizontal =\r\n align === 'right'\r\n ? { right: Math.min(Math.max(8, vw - r.right), vw - 8 - width) }\r\n : { left: Math.min(Math.max(8, r.left), vw - 8 - width) }\r\n const fitsBelow = r.bottom + 4 + height <= window.innerHeight - 8\r\n setPos({\r\n ...horizontal,\r\n minWidth: Math.min(r.width, vw - 16),\r\n maxWidth: vw - 16,\r\n ...(fitsBelow || r.top < height ? { top: r.bottom + 4 } : { bottom: window.innerHeight - r.top + 4 }),\r\n })\r\n }, [open, align])\r\n\r\n useEffect(() => {\r\n if (!open) return\r\n const click = (e: MouseEvent) => {\r\n const t = e.target as Node\r\n if (!triggerRef.current?.contains(t) && !panelRef.current?.contains(t)) setOpen(false)\r\n }\r\n const scroll = (e: Event) => {\r\n if (!panelRef.current?.contains(e.target as Node)) setOpen(false)\r\n }\r\n const resize = () => setOpen(false)\r\n document.addEventListener('mousedown', click)\r\n window.addEventListener('scroll', scroll, true)\r\n window.addEventListener('resize', resize)\r\n return () => {\r\n document.removeEventListener('mousedown', click)\r\n window.removeEventListener('scroll', scroll, true)\r\n window.removeEventListener('resize', resize)\r\n }\r\n }, [open])\r\n\r\n // Antes de medir, o painel desenha-se invisível (para se saber a altura).\r\n const style: CSSProperties = pos ?? { top: 0, left: 0, visibility: 'hidden' }\r\n return { open, setOpen, close, triggerRef, panelRef, style }\r\n}\r\n"],"names":[],"mappings":";AAMO,MAAM,cACX;AAGK,MAAM,YAAY,CAAC,QAAiB,SAAS,UAClD,mFACE,SAAS,mCAAmC,oCAC9C,IAAI,SAAS,mCAAmC,0DAA0D;AAMrG,SAAS,iBAAiB,QAA0B,QAAQ;AACjE,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,WAAW,OAAuB,IAAI;AAC5C,QAAM,CAAC,KAAK,MAAM,IAAI,SAA+B,IAAI;AAEzD,QAAM,QAAQ,YAAY,MAAM,QAAQ,KAAK,GAAG,CAAA,CAAE;AAElD,kBAAgB,MAAM;AACpB,QAAI,CAAC,QAAQ,CAAC,WAAW,SAAS;AAChC,aAAO,IAAI;AACX;AAAA,IACF;AACA,UAAM,IAAI,WAAW,QAAQ,sBAAA;AAC7B,UAAM,SAAS,SAAS,SAAS,gBAAgB;AAGjD,UAAM,KAAK,OAAO;AAClB,UAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,SAAS,SAAS,eAAe,GAAG,EAAE,KAAK,GAAG,KAAK,EAAE;AACrF,UAAM,aACJ,UAAU,UACN,EAAE,OAAO,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,KAAK,EAAA,IAC3D,EAAE,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,EAAE,IAAI,GAAG,KAAK,IAAI,KAAK,EAAA;AAC1D,UAAM,YAAY,EAAE,SAAS,IAAI,UAAU,OAAO,cAAc;AAChE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU,KAAK,IAAI,EAAE,OAAO,KAAK,EAAE;AAAA,MACnC,UAAU,KAAK;AAAA,MACf,GAAI,aAAa,EAAE,MAAM,SAAS,EAAE,KAAK,EAAE,SAAS,EAAA,IAAM,EAAE,QAAQ,OAAO,cAAc,EAAE,MAAM,EAAA;AAAA,IAAE,CACpG;AAAA,EACH,GAAG,CAAC,MAAM,KAAK,CAAC;AAEhB,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,CAAC,MAAkB;AAC/B,YAAM,IAAI,EAAE;AACZ,UAAI,CAAC,WAAW,SAAS,SAAS,CAAC,KAAK,CAAC,SAAS,SAAS,SAAS,CAAC,WAAW,KAAK;AAAA,IACvF;AACA,UAAM,SAAS,CAAC,MAAa;AAC3B,UAAI,CAAC,SAAS,SAAS,SAAS,EAAE,MAAc,WAAW,KAAK;AAAA,IAClE;AACA,UAAM,SAAS,MAAM,QAAQ,KAAK;AAClC,aAAS,iBAAiB,aAAa,KAAK;AAC5C,WAAO,iBAAiB,UAAU,QAAQ,IAAI;AAC9C,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,KAAK;AAC/C,aAAO,oBAAoB,UAAU,QAAQ,IAAI;AACjD,aAAO,oBAAoB,UAAU,MAAM;AAAA,IAC7C;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAGT,QAAM,QAAuB,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,SAAA;AACnE,SAAO,EAAE,MAAM,SAAS,OAAO,YAAY,UAAU,MAAA;AACvD;"}
@@ -0,0 +1,301 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { Chart, registerables } from "chart.js";
4
+ import ChartDataLabels from "chartjs-plugin-datalabels";
5
+ import { Bar, Doughnut, Line } from "react-chartjs-2";
6
+ import { useIsDark } from "../../../capabilities/theme/useIsDark.js";
7
+ Chart.register(...registerables, ChartDataLabels);
8
+ Chart.defaults.set("plugins.datalabels", { display: false });
9
+ Chart.defaults.font.family = "'Montserrat', sans-serif";
10
+ const PALETTE_LIGHT = ["#1868B0", "#1F8A55", "#B96E00", "#C23B34", "#7C3AED", "#0891B2", "#DB2777", "#65A30D", "#4B5563", "#D97706", "#2563EB", "#059669"];
11
+ const PALETTE_DARK = ["#60A5FA", "#34D399", "#FBBF24", "#F87171", "#A78BFA", "#22D3EE", "#F472B6", "#A3E635", "#94A3B8", "#FB923C", "#93C5FD", "#6EE7B7"];
12
+ function useChartPalette() {
13
+ const dark = useIsDark();
14
+ return useMemo(() => {
15
+ const fallback = dark ? PALETTE_DARK : PALETTE_LIGHT;
16
+ if (typeof document === "undefined") return fallback;
17
+ const css = getComputedStyle(document.documentElement);
18
+ return fallback.map((f, i) => css.getPropertyValue(`--color-chart-${i + 1}`).trim() || f);
19
+ }, [dark]);
20
+ }
21
+ function useChartTheme() {
22
+ const dark = useIsDark();
23
+ return dark ? { text: "#CBD5E1", strong: "#E2E8F0", grid: "#1E293B", tipBg: "#0F172A", tipBorder: "#334155" } : { text: "#5B6577", strong: "#182238", grid: "#EEF1F5", tipBg: "#FFFFFF", tipBorder: "#E2E8F0" };
24
+ }
25
+ function fade(color, alpha = 0.3) {
26
+ return /^#[0-9a-f]{6}$/i.test(color) ? `${color}${Math.round(alpha * 255).toString(16).padStart(2, "0")}` : color;
27
+ }
28
+ function shortDateLabel(b) {
29
+ if (/^\d{4}-S\d{2}$/.test(b)) return `S${b.slice(6)}/${b.slice(2, 4)}`;
30
+ if (/^\d{4}-\d{2}$/.test(b)) return `${b.slice(5)}/${b.slice(0, 4)}`;
31
+ if (/^\d{4}-\d{2}-\d{2}$/.test(b)) return `${b.slice(8)}/${b.slice(5, 7)}`;
32
+ return b;
33
+ }
34
+ function tooltipBase(t) {
35
+ return {
36
+ backgroundColor: t.tipBg,
37
+ borderColor: t.tipBorder,
38
+ borderWidth: 1,
39
+ titleColor: t.strong,
40
+ bodyColor: t.text,
41
+ padding: 10,
42
+ cornerRadius: 8,
43
+ boxPadding: 4,
44
+ usePointStyle: true
45
+ };
46
+ }
47
+ function legendBottom(t) {
48
+ return {
49
+ position: "bottom",
50
+ labels: { boxWidth: 10, font: { size: 11 }, color: t.strong, usePointStyle: true, pointStyle: "circle" }
51
+ };
52
+ }
53
+ function clickHandlers(labels, series, onClick) {
54
+ if (!onClick) return {};
55
+ return {
56
+ onClick: (_e, els) => {
57
+ const el = els[0];
58
+ if (el) onClick(labels[el.index], el.index, series?.[el.datasetIndex]?.name);
59
+ },
60
+ onHover: (e, els) => {
61
+ const target = e.native?.target;
62
+ if (target) target.style.cursor = els.length ? "pointer" : "default";
63
+ }
64
+ };
65
+ }
66
+ function ChartFrame({
67
+ height,
68
+ loading,
69
+ empty,
70
+ emptyLabel,
71
+ ariaLabel,
72
+ className,
73
+ children
74
+ }) {
75
+ return /* @__PURE__ */ jsx(
76
+ "div",
77
+ {
78
+ role: "img",
79
+ "aria-label": ariaLabel,
80
+ "aria-busy": loading || void 0,
81
+ className: `relative ${className ?? ""}`,
82
+ style: { height },
83
+ children: loading ? /* @__PURE__ */ jsx("div", { className: "h-full w-full animate-pulse rounded-lg bg-muted" }) : empty ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground", children: emptyLabel }) : children
84
+ }
85
+ );
86
+ }
87
+ const allZero = (values) => values.every((v) => !v);
88
+ function BarChart({
89
+ labels: allLabels,
90
+ series: allSeries,
91
+ horizontal = false,
92
+ stacked = false,
93
+ height,
94
+ limit,
95
+ colors,
96
+ format = (v) => String(v),
97
+ showValues = true,
98
+ showPercent = false,
99
+ legend,
100
+ formatLabel = shortDateLabel,
101
+ onClick,
102
+ activeLabel,
103
+ loading,
104
+ emptyLabel = "Sem dados",
105
+ ariaLabel,
106
+ className
107
+ }) {
108
+ const t = useChartTheme();
109
+ const palette = useChartPalette();
110
+ const labels = limit ? allLabels.slice(0, limit) : allLabels;
111
+ const series = allSeries.map((s) => ({ ...s, values: limit ? s.values.slice(0, limit) : s.values }));
112
+ const single = series.length === 1;
113
+ const total = single ? allSeries[0].values.reduce((a, b) => a + (b || 0), 0) : 0;
114
+ const valueLabel = (v) => showPercent && single && total > 0 ? `${format(v)} (${Math.round(v / total * 100)}%)` : format(v);
115
+ const finalHeight = height ?? (horizontal ? Math.max(200, labels.length * 30 + 40) : 260);
116
+ const valueAxis = { beginAtZero: true, stacked, grid: { color: t.grid }, ticks: { color: t.text, callback: (v) => format(Number(v)) } };
117
+ const labelAxis = { stacked, grid: { display: false }, ticks: { color: t.text } };
118
+ const dim = (c, j) => activeLabel != null && labels[j] !== activeLabel ? fade(c) : c;
119
+ const empty = labels.length === 0 || series.every((s) => allZero(s.values));
120
+ return /* @__PURE__ */ jsx(ChartFrame, { height: finalHeight, loading, empty, emptyLabel, ariaLabel, className, children: /* @__PURE__ */ jsx(
121
+ Bar,
122
+ {
123
+ data: {
124
+ labels: labels.map(formatLabel),
125
+ datasets: series.map((s, i) => {
126
+ const base = s.color ?? palette[i % palette.length];
127
+ return {
128
+ label: s.name,
129
+ data: s.values,
130
+ backgroundColor: labels.map((_, j) => dim(single && colors ? colors[j % colors.length] : base, j)),
131
+ borderRadius: 4,
132
+ maxBarThickness: 40
133
+ };
134
+ })
135
+ },
136
+ options: {
137
+ indexAxis: horizontal ? "y" : "x",
138
+ maintainAspectRatio: false,
139
+ responsive: true,
140
+ ...clickHandlers(labels, series, onClick),
141
+ plugins: {
142
+ legend: legend ?? !single ? legendBottom(t) : { display: false },
143
+ tooltip: {
144
+ ...tooltipBase(t),
145
+ callbacks: { label: (c) => ` ${c.dataset.label ? `${c.dataset.label}: ` : ""}${valueLabel(Number(c.parsed[horizontal ? "x" : "y"]))}` }
146
+ },
147
+ datalabels: {
148
+ display: (c) => showValues && !stacked && Number(c.dataset.data[c.dataIndex]) > 0,
149
+ anchor: "end",
150
+ align: "end",
151
+ color: t.strong,
152
+ font: { size: 10, weight: 600 },
153
+ formatter: (v) => valueLabel(v)
154
+ }
155
+ },
156
+ scales: horizontal ? { x: valueAxis, y: labelAxis } : { x: labelAxis, y: valueAxis },
157
+ layout: { padding: horizontal ? { right: showValues ? showPercent ? 56 : 32 : 8 } : { top: showValues ? 18 : 4 } }
158
+ }
159
+ }
160
+ ) });
161
+ }
162
+ function DonutChart({
163
+ labels,
164
+ values,
165
+ height = 260,
166
+ colors,
167
+ format = (v) => String(v),
168
+ centerLabel,
169
+ onClick,
170
+ activeLabel,
171
+ loading,
172
+ emptyLabel = "Sem dados",
173
+ ariaLabel,
174
+ className
175
+ }) {
176
+ const t = useChartTheme();
177
+ const palette = useChartPalette();
178
+ const total = values.reduce((a, b) => a + (b || 0), 0);
179
+ const center = centerLabel === true ? format(total) : centerLabel || null;
180
+ const centerPlugin = {
181
+ id: "v1CenterLabel",
182
+ afterDraw(chart) {
183
+ if (!center) return;
184
+ const { ctx, chartArea } = chart;
185
+ ctx.save();
186
+ ctx.font = "700 18px 'Montserrat', sans-serif";
187
+ ctx.fillStyle = t.strong;
188
+ ctx.textAlign = "center";
189
+ ctx.textBaseline = "middle";
190
+ ctx.fillText(center, (chartArea.left + chartArea.right) / 2, (chartArea.top + chartArea.bottom) / 2);
191
+ ctx.restore();
192
+ }
193
+ };
194
+ return /* @__PURE__ */ jsx(ChartFrame, { height, loading, empty: labels.length === 0 || allZero(values), emptyLabel, ariaLabel, className, children: /* @__PURE__ */ jsx(
195
+ Doughnut,
196
+ {
197
+ data: {
198
+ labels,
199
+ datasets: [
200
+ {
201
+ data: values,
202
+ backgroundColor: labels.map((l, i) => {
203
+ const c = colors?.[i % colors.length] ?? palette[i % palette.length];
204
+ return activeLabel != null && l !== activeLabel ? fade(c) : c;
205
+ }),
206
+ borderWidth: 0
207
+ }
208
+ ]
209
+ },
210
+ plugins: [centerPlugin],
211
+ options: {
212
+ maintainAspectRatio: false,
213
+ responsive: true,
214
+ cutout: "58%",
215
+ ...clickHandlers(labels, void 0, onClick),
216
+ plugins: {
217
+ legend: legendBottom(t),
218
+ tooltip: {
219
+ ...tooltipBase(t),
220
+ callbacks: {
221
+ label: (c) => {
222
+ const v = Number(c.parsed);
223
+ return ` ${c.label}: ${format(v)}${total > 0 ? ` (${Math.round(v / total * 100)}%)` : ""}`;
224
+ }
225
+ }
226
+ },
227
+ datalabels: {
228
+ // Só nas fatias com espaço para o número.
229
+ display: (c) => total > 0 && Number(c.dataset.data[c.dataIndex]) / total >= 0.05,
230
+ color: "#FFFFFF",
231
+ font: { size: 11, weight: 700 },
232
+ formatter: (v) => format(v)
233
+ }
234
+ }
235
+ }
236
+ }
237
+ ) });
238
+ }
239
+ function LineChart({
240
+ labels,
241
+ series,
242
+ height = 280,
243
+ area = false,
244
+ colors,
245
+ format = (v) => String(v),
246
+ formatLabel = shortDateLabel,
247
+ legend,
248
+ onClick,
249
+ loading,
250
+ emptyLabel = "Sem dados",
251
+ ariaLabel,
252
+ className
253
+ }) {
254
+ const t = useChartTheme();
255
+ const palette = useChartPalette();
256
+ const colorOf = (i) => series[i]?.color ?? colors?.[i] ?? palette[i % palette.length];
257
+ const empty = labels.length === 0 || series.every((s) => allZero(s.values));
258
+ return /* @__PURE__ */ jsx(ChartFrame, { height, loading, empty, emptyLabel, ariaLabel, className, children: /* @__PURE__ */ jsx(
259
+ Line,
260
+ {
261
+ data: {
262
+ labels: labels.map(formatLabel),
263
+ datasets: series.map((s, i) => ({
264
+ label: s.name,
265
+ data: s.values,
266
+ borderColor: colorOf(i),
267
+ backgroundColor: area ? fade(colorOf(i), 0.13) : colorOf(i),
268
+ fill: area,
269
+ tension: 0.25,
270
+ pointRadius: labels.length > 24 ? 0 : 3,
271
+ pointHoverRadius: 4
272
+ }))
273
+ },
274
+ options: {
275
+ maintainAspectRatio: false,
276
+ responsive: true,
277
+ interaction: { mode: "index", intersect: false },
278
+ ...clickHandlers(labels, series, onClick),
279
+ plugins: {
280
+ legend: legend ?? series.length > 1 ? legendBottom(t) : { display: false },
281
+ tooltip: {
282
+ ...tooltipBase(t),
283
+ callbacks: { label: (c) => ` ${c.dataset.label ? `${c.dataset.label}: ` : ""}${format(Number(c.parsed.y))}` }
284
+ }
285
+ },
286
+ scales: {
287
+ x: { grid: { display: false }, ticks: { color: t.text, autoSkip: true, maxRotation: 0 } },
288
+ y: { beginAtZero: true, grid: { color: t.grid }, ticks: { color: t.text, callback: (v) => format(Number(v)) } }
289
+ }
290
+ }
291
+ }
292
+ ) });
293
+ }
294
+ export {
295
+ BarChart,
296
+ DonutChart,
297
+ LineChart,
298
+ shortDateLabel,
299
+ useChartPalette
300
+ };
301
+ //# sourceMappingURL=Charts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Charts.js","sources":["../../../../../../src/v1/components/data/Charts/Charts.tsx"],"sourcesContent":["import React, { useMemo } from 'react'\r\nimport { Chart as ChartJS, registerables, type ActiveElement, type ChartEvent, type Plugin } from 'chart.js'\r\nimport ChartDataLabels from 'chartjs-plugin-datalabels'\r\nimport { Bar, Doughnut, Line } from 'react-chartjs-2'\r\nimport { useIsDark } from '../../../capabilities/theme/useIsDark'\r\n\r\n// Regista tudo (registerables = chart.js/auto) + o plugin das etiquetas de valor. O registo\r\n// parcial colapsava o anel e avisava \"fill sem Filler\". Registar duas vezes não faz mal.\r\nChartJS.register(...registerables, ChartDataLabels)\r\nChartJS.defaults.set('plugins.datalabels', { display: false })\r\nChartJS.defaults.font.family = \"'Montserrat', sans-serif\"\r\n\r\n/** Uma série: nome (legenda e tooltip), um valor por etiqueta e, opcionalmente, a cor. */\r\nexport interface ChartSeries {\r\n name: string\r\n values: number[]\r\n color?: string\r\n}\r\n\r\n/** Clique num elemento: a etiqueta (ex: \"Incidente\"), a posição e a série (quando há várias). */\r\nexport type ChartClick = (label: string, index: number, series?: string) => void\r\n\r\ninterface ChartBaseProps {\r\n /** Altura em px. */\r\n height?: number\r\n /** A carregar: mostra um bloco a pulsar com a altura do gráfico (a página não salta). */\r\n loading?: boolean\r\n /** Texto quando não há dados ou é tudo zero. */\r\n emptyLabel?: string\r\n /** Descrição para leitores de ecrã (ex: \"Tickets por estado\"). */\r\n ariaLabel?: string\r\n /** Clique num elemento — tipicamente filtra a página. */\r\n onClick?: ChartClick\r\n /**\r\n * Etiqueta selecionada (ex: o filtro ativo): as outras ficam esbatidas. Com `onClick`,\r\n * mostra o que o clique filtrou sem precisar de outra legenda.\r\n */\r\n activeLabel?: string | null\r\n className?: string\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Cores\r\n// ---------------------------------------------------------------------------\r\n\r\n// Mesmas cores dos tokens --color-chart-1..12 (theme.css); servem de reserva quando a\r\n// app não carrega o tema (ex: testes). Ordem fixa: a mesma dimensão tem sempre a mesma cor.\r\nconst PALETTE_LIGHT = ['#1868B0', '#1F8A55', '#B96E00', '#C23B34', '#7C3AED', '#0891B2', '#DB2777', '#65A30D', '#4B5563', '#D97706', '#2563EB', '#059669']\r\nconst PALETTE_DARK = ['#60A5FA', '#34D399', '#FBBF24', '#F87171', '#A78BFA', '#22D3EE', '#F472B6', '#A3E635', '#94A3B8', '#FB923C', '#93C5FD', '#6EE7B7']\r\n\r\n/**\r\n * A paleta dos gráficos no tema atual (lida dos tokens `--color-chart-N`). Para legendas\r\n * ou indicadores fora do gráfico com as mesmas cores das séries.\r\n */\r\nexport function useChartPalette(): string[] {\r\n const dark = useIsDark()\r\n return useMemo(() => {\r\n const fallback = dark ? PALETTE_DARK : PALETTE_LIGHT\r\n if (typeof document === 'undefined') return fallback\r\n const css = getComputedStyle(document.documentElement)\r\n return fallback.map((f, i) => css.getPropertyValue(`--color-chart-${i + 1}`).trim() || f)\r\n }, [dark])\r\n}\r\n\r\n/** Texto, grelha e tooltip conforme o tema: as cores do claro ficavam ilegíveis no escuro. */\r\nfunction useChartTheme() {\r\n const dark = useIsDark()\r\n return dark\r\n ? { text: '#CBD5E1', strong: '#E2E8F0', grid: '#1E293B', tipBg: '#0F172A', tipBorder: '#334155' }\r\n : { text: '#5B6577', strong: '#182238', grid: '#EEF1F5', tipBg: '#FFFFFF', tipBorder: '#E2E8F0' }\r\n}\r\n\r\n/** Cor esbatida (hex de 6 dígitos ganha transparência; outras ficam como estão). */\r\nfunction fade(color: string, alpha = 0.3): string {\r\n return /^#[0-9a-f]{6}$/i.test(color) ? `${color}${Math.round(alpha * 255).toString(16).padStart(2, '0')}` : color\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Peças comuns\r\n// ---------------------------------------------------------------------------\r\n\r\n/** 'YYYY-Sxx' / 'YYYY-MM' / 'YYYY-MM-DD' → etiqueta curta no eixo; o resto fica igual. */\r\nexport function shortDateLabel(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\nfunction tooltipBase(t: ReturnType<typeof useChartTheme>) {\r\n return {\r\n backgroundColor: t.tipBg,\r\n borderColor: t.tipBorder,\r\n borderWidth: 1,\r\n titleColor: t.strong,\r\n bodyColor: t.text,\r\n padding: 10,\r\n cornerRadius: 8,\r\n boxPadding: 4,\r\n usePointStyle: true,\r\n }\r\n}\r\n\r\nfunction legendBottom(t: ReturnType<typeof useChartTheme>) {\r\n return {\r\n position: 'bottom' as const,\r\n labels: { boxWidth: 10, font: { size: 11 }, color: t.strong, usePointStyle: true, pointStyle: 'circle' as const },\r\n }\r\n}\r\n\r\n/** Liga o clique do Chart.js ao `onClick`, com a mão por cima dos elementos. */\r\nfunction clickHandlers(labels: string[], series: { name: string }[] | undefined, onClick?: ChartClick) {\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) onClick(labels[el.index]!, el.index, series?.[el.datasetIndex]?.name)\r\n },\r\n onHover: (e: ChartEvent, els: ActiveElement[]) => {\r\n const target = e.native?.target as HTMLElement | undefined\r\n if (target) target.style.cursor = els.length ? 'pointer' : 'default'\r\n },\r\n }\r\n}\r\n\r\n/** Moldura de todos os gráficos: altura fixa, estado a carregar e estado vazio. */\r\nfunction ChartFrame({\r\n height,\r\n loading,\r\n empty,\r\n emptyLabel,\r\n ariaLabel,\r\n className,\r\n children,\r\n}: {\r\n height: number\r\n loading?: boolean\r\n empty: boolean\r\n emptyLabel: string\r\n ariaLabel?: string\r\n className?: string\r\n children: React.ReactNode\r\n}) {\r\n return (\r\n <div\r\n role=\"img\"\r\n aria-label={ariaLabel}\r\n aria-busy={loading || undefined}\r\n className={`relative ${className ?? ''}`}\r\n style={{ height }}\r\n >\r\n {loading ? (\r\n <div className=\"h-full w-full animate-pulse rounded-lg bg-muted\" />\r\n ) : empty ? (\r\n <div className=\"flex h-full items-center justify-center rounded-lg border border-dashed border-border text-sm text-muted-foreground\">\r\n {emptyLabel}\r\n </div>\r\n ) : (\r\n children\r\n )}\r\n </div>\r\n )\r\n}\r\n\r\nconst allZero = (values: number[]) => values.every((v) => !v)\r\n\r\n// ---------------------------------------------------------------------------\r\n// BarChart\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface BarChartProps extends ChartBaseProps {\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: ChartSeries[]\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 stacked?: boolean\r\n /** Só as primeiras N (ex: top 10). */\r\n limit?: number\r\n /** Cor de cada barra, quando há uma só série (ex: a cor de cada estado). */\r\n colors?: string[]\r\n /** Formato dos valores no tooltip, nas etiquetas e no eixo (ex: minutos → \"7h30\"). */\r\n format?: (v: number) => string\r\n /** Valor na ponta de cada barra (não nas empilhadas). */\r\n showValues?: boolean\r\n /** Junta a percentagem do total ao valor (ex: \"12 (34%)\"); só com uma série. */\r\n showPercent?: boolean\r\n /** Legenda das séries. Por omissão, só quando há mais do que uma. */\r\n legend?: boolean\r\n /** Formato das etiquetas do eixo. Por omissão, datas ISO ficam curtas (`shortDateLabel`). */\r\n formatLabel?: (label: string) => string\r\n}\r\n\r\n/**\r\n * Barras verticais ou deitadas, de uma ou várias séries, lado a lado ou empilhadas.\r\n *\r\n * Clique numa barra com `onClick`; com `activeLabel`, as outras ficam esbatidas (o filtro\r\n * aplicado lê-se no próprio gráfico). Sem dados, mostra `emptyLabel`; a carregar, um bloco.\r\n */\r\nexport function BarChart({\r\n labels: allLabels,\r\n series: allSeries,\r\n horizontal = false,\r\n stacked = false,\r\n height,\r\n limit,\r\n colors,\r\n format = (v) => String(v),\r\n showValues = true,\r\n showPercent = false,\r\n legend,\r\n formatLabel = shortDateLabel,\r\n onClick,\r\n activeLabel,\r\n loading,\r\n emptyLabel = 'Sem dados',\r\n ariaLabel,\r\n className,\r\n}: BarChartProps) {\r\n const t = useChartTheme()\r\n const palette = useChartPalette()\r\n const labels = limit ? allLabels.slice(0, limit) : allLabels\r\n const series = allSeries.map((s) => ({ ...s, values: limit ? s.values.slice(0, limit) : s.values }))\r\n const single = series.length === 1\r\n // A percentagem é do total, não só das barras mostradas (com `limit` seria das top N).\r\n const total = single ? allSeries[0]!.values.reduce((a, b) => a + (b || 0), 0) : 0\r\n const valueLabel = (v: number) => (showPercent && single && total > 0 ? `${format(v)} (${Math.round((v / total) * 100)}%)` : format(v))\r\n const finalHeight = height ?? (horizontal ? Math.max(200, labels.length * 30 + 40) : 260)\r\n const valueAxis = { beginAtZero: true, stacked, grid: { color: t.grid }, ticks: { color: t.text, callback: (v: string | number) => format(Number(v)) } }\r\n const labelAxis = { stacked, grid: { display: false }, ticks: { color: t.text } }\r\n const dim = (c: string, j: number) => (activeLabel != null && labels[j] !== activeLabel ? fade(c) : c)\r\n const empty = labels.length === 0 || series.every((s) => allZero(s.values))\r\n\r\n return (\r\n <ChartFrame height={finalHeight} loading={loading} empty={empty} emptyLabel={emptyLabel} ariaLabel={ariaLabel} className={className}>\r\n <Bar\r\n data={{\r\n labels: labels.map(formatLabel),\r\n datasets: series.map((s, i) => {\r\n const base = s.color ?? palette[i % palette.length]!\r\n return {\r\n label: s.name,\r\n data: s.values,\r\n backgroundColor: labels.map((_, j) => dim(single && colors ? colors[j % colors.length]! : base, j)),\r\n borderRadius: 4,\r\n maxBarThickness: 40,\r\n }\r\n }),\r\n }}\r\n options={{\r\n indexAxis: horizontal ? 'y' : 'x',\r\n maintainAspectRatio: false,\r\n responsive: true,\r\n ...clickHandlers(labels, series, onClick),\r\n plugins: {\r\n legend: (legend ?? !single) ? legendBottom(t) : { display: false },\r\n tooltip: {\r\n ...tooltipBase(t),\r\n callbacks: { label: (c) => ` ${c.dataset.label ? `${c.dataset.label}: ` : ''}${valueLabel(Number(c.parsed[horizontal ? 'x' : 'y']))}` },\r\n },\r\n datalabels: {\r\n display: (c) => showValues && !stacked && Number(c.dataset.data[c.dataIndex]) > 0,\r\n anchor: 'end',\r\n align: 'end',\r\n color: t.strong,\r\n font: { size: 10, weight: 600 },\r\n formatter: (v: number) => valueLabel(v),\r\n },\r\n },\r\n scales: horizontal ? { x: valueAxis, y: labelAxis } : { x: labelAxis, y: valueAxis },\r\n layout: { padding: horizontal ? { right: showValues ? (showPercent ? 56 : 32) : 8 } : { top: showValues ? 18 : 4 } },\r\n }}\r\n />\r\n </ChartFrame>\r\n )\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// DonutChart\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface DonutChartProps extends ChartBaseProps {\r\n labels: string[]\r\n values: number[]\r\n /** Cor de cada fatia (ex: a cor de cada estado). Por omissão, a paleta. */\r\n colors?: string[]\r\n /** Formato dos valores no tooltip e nas fatias. */\r\n format?: (v: number) => string\r\n /**\r\n * Texto no centro do anel. `true` mostra o total (com `format`); um texto mostra esse\r\n * texto (ex: \"128 tickets\").\r\n */\r\n centerLabel?: boolean | string\r\n}\r\n\r\n/**\r\n * Anel de uma distribuição (ex: tickets por estado). Percentagem no tooltip, valor nas\r\n * fatias com espaço para ele e, com `centerLabel`, o total no meio.\r\n */\r\nexport function DonutChart({\r\n labels,\r\n values,\r\n height = 260,\r\n colors,\r\n format = (v) => String(v),\r\n centerLabel,\r\n onClick,\r\n activeLabel,\r\n loading,\r\n emptyLabel = 'Sem dados',\r\n ariaLabel,\r\n className,\r\n}: DonutChartProps) {\r\n const t = useChartTheme()\r\n const palette = useChartPalette()\r\n const total = values.reduce((a, b) => a + (b || 0), 0)\r\n const center = centerLabel === true ? format(total) : centerLabel || null\r\n // O texto do centro é desenhado no canvas, no meio da área do anel (a legenda fica de fora).\r\n const centerPlugin: Plugin<'doughnut'> = {\r\n id: 'v1CenterLabel',\r\n afterDraw(chart) {\r\n if (!center) return\r\n const { ctx, chartArea } = chart\r\n ctx.save()\r\n ctx.font = \"700 18px 'Montserrat', sans-serif\"\r\n ctx.fillStyle = t.strong\r\n ctx.textAlign = 'center'\r\n ctx.textBaseline = 'middle'\r\n ctx.fillText(center, (chartArea.left + chartArea.right) / 2, (chartArea.top + chartArea.bottom) / 2)\r\n ctx.restore()\r\n },\r\n }\r\n\r\n return (\r\n <ChartFrame height={height} loading={loading} empty={labels.length === 0 || allZero(values)} emptyLabel={emptyLabel} ariaLabel={ariaLabel} className={className}>\r\n <Doughnut\r\n data={{\r\n labels,\r\n datasets: [\r\n {\r\n data: values,\r\n backgroundColor: labels.map((l, i) => {\r\n const c = colors?.[i % colors.length] ?? palette[i % palette.length]!\r\n return activeLabel != null && l !== activeLabel ? fade(c) : c\r\n }),\r\n borderWidth: 0,\r\n },\r\n ],\r\n }}\r\n plugins={[centerPlugin]}\r\n options={{\r\n maintainAspectRatio: false,\r\n responsive: true,\r\n cutout: '58%',\r\n ...clickHandlers(labels, undefined, onClick),\r\n plugins: {\r\n legend: legendBottom(t),\r\n tooltip: {\r\n ...tooltipBase(t),\r\n callbacks: {\r\n label: (c) => {\r\n const v = Number(c.parsed)\r\n return ` ${c.label}: ${format(v)}${total > 0 ? ` (${Math.round((v / total) * 100)}%)` : ''}`\r\n },\r\n },\r\n },\r\n datalabels: {\r\n // Só nas fatias com 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) => format(v),\r\n },\r\n },\r\n }}\r\n />\r\n </ChartFrame>\r\n )\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// LineChart\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface LineChartProps extends ChartBaseProps {\r\n labels: string[]\r\n /**\r\n * Uma ou várias linhas sobre as mesmas etiquetas — NÃO empilhadas: duas séries que medem\r\n * o mesmo em dois momentos (criados vs fechados) leem-se uma contra a outra.\r\n */\r\n series: ChartSeries[]\r\n /** Área por baixo de cada linha. Só se lê bem com uma ou duas séries. */\r\n area?: boolean\r\n /** Cor de cada série, pela ordem (ou `color` na série). */\r\n colors?: string[]\r\n /** Formato dos valores no tooltip e no eixo. */\r\n format?: (v: number) => string\r\n /** Formato das etiquetas do eixo. Por omissão, datas ISO ficam curtas. */\r\n formatLabel?: (label: string) => string\r\n /** Legenda das séries. Por omissão, só quando há mais do que uma. */\r\n legend?: boolean\r\n}\r\n\r\n/** Linhas sobre o tempo (ou outra sequência), com o tooltip de todas as séries no mesmo ponto. */\r\nexport function LineChart({\r\n labels,\r\n series,\r\n height = 280,\r\n area = false,\r\n colors,\r\n format = (v) => String(v),\r\n formatLabel = shortDateLabel,\r\n legend,\r\n onClick,\r\n loading,\r\n emptyLabel = 'Sem dados',\r\n ariaLabel,\r\n className,\r\n}: LineChartProps) {\r\n const t = useChartTheme()\r\n const palette = useChartPalette()\r\n const colorOf = (i: number) => series[i]?.color ?? colors?.[i] ?? palette[i % palette.length]!\r\n const empty = labels.length === 0 || series.every((s) => allZero(s.values))\r\n\r\n return (\r\n <ChartFrame height={height} loading={loading} empty={empty} emptyLabel={emptyLabel} ariaLabel={ariaLabel} className={className}>\r\n <Line\r\n data={{\r\n labels: labels.map(formatLabel),\r\n datasets: series.map((s, i) => ({\r\n label: s.name,\r\n data: s.values,\r\n borderColor: colorOf(i),\r\n backgroundColor: area ? fade(colorOf(i), 0.13) : colorOf(i),\r\n fill: area,\r\n tension: 0.25,\r\n pointRadius: 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 ...clickHandlers(labels, series, onClick),\r\n plugins: {\r\n legend: (legend ?? series.length > 1) ? legendBottom(t) : { display: false },\r\n tooltip: {\r\n ...tooltipBase(t),\r\n callbacks: { label: (c) => ` ${c.dataset.label ? `${c.dataset.label}: ` : ''}${format(Number(c.parsed.y))}` },\r\n },\r\n },\r\n scales: {\r\n x: { grid: { display: false }, ticks: { color: t.text, autoSkip: true, maxRotation: 0 } },\r\n y: { beginAtZero: true, grid: { color: t.grid }, ticks: { color: t.text, callback: (v) => format(Number(v)) } },\r\n },\r\n }}\r\n />\r\n </ChartFrame>\r\n )\r\n}\r\n"],"names":["ChartJS"],"mappings":"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"}
@@ -28,35 +28,39 @@ function Pagination({
28
28
  const L = { ...DEFAULT_PAGINATION_LABELS, ...labels };
29
29
  const totalPaginas = Math.max(1, pages);
30
30
  const opcoes = pageSizeOptions && pageSize != null ? [.../* @__PURE__ */ new Set([...pageSizeOptions, pageSize])].sort((a, b) => a - b) : [];
31
- return /* @__PURE__ */ jsxs("nav", { "aria-label": L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas)), className: `flex flex-wrap items-center justify-between gap-2 ${className}`, children: [
32
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-black uppercase tracking-widest text-muted-foreground", children: [
33
- total != null && /* @__PURE__ */ jsxs(Fragment, { children: [
34
- L.total.replace("{count}", total.toLocaleString()),
35
- " · "
31
+ return (
32
+ // No telemóvel numa só linha: o texto à esquerda e as setas à direita, só com o ícone (com o texto
33
+ // "Anterior"/"Seguinte" partia-se em duas linhas encostadas à esquerda); sem o seletor por página.
34
+ /* @__PURE__ */ jsxs("nav", { "aria-label": L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas)), className: `flex flex-wrap items-center justify-between gap-2 max-sm:flex-nowrap ${className}`, children: [
35
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 text-[10px] font-black uppercase tracking-widest text-muted-foreground max-sm:tracking-wider", children: [
36
+ total != null && /* @__PURE__ */ jsxs(Fragment, { children: [
37
+ L.total.replace("{count}", total.toLocaleString()),
38
+ " · "
39
+ ] }),
40
+ L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas))
36
41
  ] }),
37
- L.page.replace("{page}", String(page)).replace("{pages}", String(totalPaginas))
38
- ] }),
39
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
40
- opcoes.length > 0 && onPageSizeChange && /* @__PURE__ */ jsxs("label", { className: "mr-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-muted-foreground", children: [
41
- L.perPage,
42
- /* @__PURE__ */ jsx(
43
- SelectInput,
44
- {
45
- "aria-label": L.perPage,
46
- value: String(pageSize),
47
- onChange: (e) => onPageSizeChange(Number(e.target.value)),
48
- className: "w-20 normal-case tracking-normal font-normal",
49
- children: opcoes.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
50
- }
51
- )
52
- ] }),
53
- /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", icon: /* @__PURE__ */ jsx(ChevronLeft, {}), disabled: page <= 1, onClick: () => onPageChange(page - 1), children: L.prev }),
54
- /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "sm", disabled: page >= totalPaginas, onClick: () => onPageChange(page + 1), children: [
55
- L.next,
56
- /* @__PURE__ */ jsx(ChevronRight, {})
42
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5 max-sm:shrink-0 max-sm:flex-nowrap", children: [
43
+ opcoes.length > 0 && onPageSizeChange && /* @__PURE__ */ jsxs("label", { className: "mr-2 flex items-center gap-1.5 text-[10px] font-black uppercase tracking-widest text-muted-foreground max-sm:hidden", children: [
44
+ L.perPage,
45
+ /* @__PURE__ */ jsx(
46
+ SelectInput,
47
+ {
48
+ "aria-label": L.perPage,
49
+ value: String(pageSize),
50
+ onChange: (e) => onPageSizeChange(Number(e.target.value)),
51
+ className: "w-20 normal-case tracking-normal font-normal",
52
+ children: opcoes.map((n) => /* @__PURE__ */ jsx("option", { value: n, children: n }, n))
53
+ }
54
+ )
55
+ ] }),
56
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "sm", icon: /* @__PURE__ */ jsx(ChevronLeft, {}), disabled: page <= 1, onClick: () => onPageChange(page - 1), children: /* @__PURE__ */ jsx("span", { className: "max-sm:sr-only", children: L.prev }) }),
57
+ /* @__PURE__ */ jsxs(Button, { variant: "secondary", size: "sm", disabled: page >= totalPaginas, onClick: () => onPageChange(page + 1), children: [
58
+ /* @__PURE__ */ jsx("span", { className: "max-sm:sr-only", children: L.next }),
59
+ /* @__PURE__ */ jsx(ChevronRight, {})
60
+ ] })
57
61
  ] })
58
62
  ] })
59
- ] });
63
+ );
60
64
  }
61
65
  export {
62
66
  DEFAULT_PAGINATION_LABELS,