@proef/common-ui 2.2.6 → 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.
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/legacy/components/charts/Charts.js +52 -192
- package/dist/esm/legacy/components/charts/Charts.js.map +1 -1
- package/dist/esm/legacy/components/molecules/DataTable/DataTable.js +81 -24
- package/dist/esm/legacy/components/molecules/DataTable/DataTable.js.map +1 -1
- package/dist/esm/legacy/components/organisms/GanttChart/GanttChart.js +7 -4
- package/dist/esm/legacy/components/organisms/GanttChart/GanttChart.js.map +1 -1
- package/dist/esm/v1/components/data/Charts/Charts.js +301 -0
- package/dist/esm/v1/components/data/Charts/Charts.js.map +1 -0
- package/dist/esm/v1/components/overlays/Dialog/Dialog.js +4 -1
- package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
- package/dist/esm/v1/components/tables/Table/ColumnFilter.js +3 -1
- package/dist/esm/v1/components/tables/Table/ColumnFilter.js.map +1 -1
- package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js +3 -1
- package/dist/esm/v1/primitives/forms/SelectInput/SelectInput.js.map +1 -1
- package/dist/style.components.css +1 -1
- package/dist/style.css +1 -1
- package/dist/style.layered.css +1 -1
- package/dist/theme.css +28 -0
- package/dist/types/legacy/components/charts/Charts.d.ts +14 -11
- package/dist/types/legacy/components/molecules/DataTable/DataTable.d.ts +3 -0
- package/dist/types/legacy/components/organisms/GanttChart/GanttChart.d.ts +15 -1
- package/dist/types/legacy/exports/organisms.d.ts +1 -1
- package/dist/types/v1/components/data/Charts/Charts.d.ts +103 -0
- package/dist/types/v1/index.d.ts +2 -0
- 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:
|
|
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:
|
|
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":"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"}
|
|
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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"}
|
|
@@ -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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"}
|
|
@@ -102,7 +102,10 @@ function Dialog({
|
|
|
102
102
|
"aria-modal": "true",
|
|
103
103
|
"aria-labelledby": titleId,
|
|
104
104
|
tabIndex: -1,
|
|
105
|
-
className:
|
|
105
|
+
className: (
|
|
106
|
+
// No escuro a sombra não se vê: a linha (a mesma dos menus) separa a janela do fundo.
|
|
107
|
+
`relative flex max-h-full w-full ${WIDTHS[size]} flex-col overflow-hidden rounded-2xl bg-card text-foreground shadow-modal outline-none dark:border dark:border-slate-700 motion-safe:animate-dialog-in ${height === "full" ? "h-full" : ""}`
|
|
108
|
+
),
|
|
106
109
|
children: [
|
|
107
110
|
/* @__PURE__ */ jsxs("header", { className: "flex flex-none items-center justify-between gap-3 bg-gradient-to-br from-primary to-primary-strong px-5 py-3.5", children: [
|
|
108
111
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dialog.js","sources":["../../../../../../src/v1/components/overlays/Dialog/Dialog.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useId, useRef, useState } from 'react'\r\nimport { createPortal } from 'react-dom'\r\n\r\nconst DialogContainerContext = createContext<HTMLElement | null>(null)\r\nconst DialogDepthContext = createContext(0)\r\n\r\n/**\r\n * O elemento da janela, para usar como destino de `createPortal` (ex: avisos,\r\n * confirmações, sobreposições) quando o conteúdo deve ficar junto dela. Um\r\n * portal para `document.body` também funciona, desde que fique por cima\r\n * (`z-index` acima de 200). Fora de um Dialog devolve `null`.\r\n *\r\n * `createPortal(conteudo, useDialogContainer() ?? document.body)`\r\n */\r\nexport const useDialogContainer = () => useContext(DialogContainerContext)\r\n\r\nexport interface DialogProps {\r\n /** Aberta ou fechada. A app guarda este estado. */\r\n open: boolean\r\n /** Pedido para fechar (×, Esc ou clique fora): a app põe `open` a `false`. */\r\n onClose: () => void\r\n /** Normalmente texto; aceita JSX (ex: um nome em negrito). */\r\n title: React.ReactNode\r\n /** Linha por baixo do título (ex: nome · departamento). */\r\n subtitle?: React.ReactNode\r\n /** Botões no rodapé, sempre visíveis (ex: Confirmar, Fechar). */\r\n footer?: React.ReactNode\r\n /**\r\n * Largura máxima: `sm` 480px, `md` 720px, `lg` 1000px, `xl` até 1600px (95% do ecrã, para janelas com\r\n * mapa e tabela lado a lado). Nunca passa o ecrã.\r\n */\r\n size?: 'sm' | 'md' | 'lg' | 'xl'\r\n /**\r\n * Altura: `auto` (por omissão) cresce com o conteúdo; `full` ocupa sempre a\r\n * altura disponível (útil quando o conteúdo muda de tamanho e a janela não\r\n * deve saltar). Em ambos, se o conteúdo não couber, o corpo faz scroll.\r\n */\r\n height?: 'auto' | 'full'\r\n /** Fechar ao clicar fora da janela. Por omissão sim; num formulário longo pode valer a pena desligar. */\r\n closeOnBackdrop?: boolean\r\n /** Nome do botão ×, para leitores de ecrã. Por omissão: \"Fechar\". */\r\n closeLabel?: string\r\n /**\r\n * Corpo sem margens e sem scroll: o conteúdo ocupa-o todo e gere o seu próprio\r\n * scroll (ex: passos que deslizam, rodapé próprio de cada passo).\r\n */\r\n flush?: boolean\r\n children: React.ReactNode\r\n}\r\n\r\nconst WIDTHS = { sm: 'max-w-dialog-sm', md: 'max-w-dialog-md', lg: 'max-w-dialog-lg', xl: 'max-w-dialog-xl' }\r\n\r\ninterface Aberta {\r\n root: HTMLElement\r\n janela: HTMLElement | null\r\n depth: number\r\n}\r\n\r\nconst abertas: Aberta[] = []\r\nconst silenciados = new Set<Element>()\r\n\r\nconst doTopo = () => abertas[abertas.length - 1]\r\n\r\nfunction silenciar() {\r\n for (const el of silenciados) el.removeAttribute('inert')\r\n silenciados.clear()\r\n const topo = doTopo()\r\n if (!topo) return\r\n for (const el of Array.from(document.body.children)) {\r\n if (el === topo.root || el.hasAttribute('inert')) continue\r\n el.setAttribute('inert', '')\r\n silenciados.add(el)\r\n }\r\n}\r\n\r\n// Uma janela aberta dentro de outra fica sempre por cima dela, mesmo que abram juntas.\r\nfunction empilhar(aberta: Aberta) {\r\n const acima = abertas.findIndex((a) => a.depth > aberta.depth)\r\n if (acima === -1) abertas.push(aberta)\r\n else abertas.splice(acima, 0, aberta)\r\n}\r\n\r\n/**\r\n * Janela por cima da página: cabeçalho navy com título, corpo que faz scroll e\r\n * rodapé com os botões ([Button](?path=/docs/v1-primitives-buttons-button--docs)).\r\n *\r\n * É a janela modal da V1 (substitui o `Modal` legacy).\r\n *\r\n * Fundo escurecido, Esc para fechar e, ao fechar, o foco volta ao sítio onde\r\n * estava. Enquanto está aberta, o resto da página fica\r\n * [inert](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert):\r\n * nem rato, nem teclado, nem leitores de ecrã lhe chegam. Os portais abertos\r\n * depois dela (seletores, confirmações) continuam a responder.\r\n *\r\n * O conteúdo fica montado mesmo com a janela fechada, para poder carregar antes\r\n * de ela abrir.\r\n *\r\n * O tamanho adapta-se ao ecrã só com CSS: nunca passa a largura nem a altura\r\n * da janela do browser (com uma margem), e acompanha-a quando é\r\n * redimensionada. O que não couber faz scroll no corpo; o cabeçalho e o\r\n * rodapé ficam sempre visíveis.\r\n *\r\n * **Exemplo completo:** [Formulário em modal](?path=/docs/v1-patterns-forms-formulário-em-modal--docs).\r\n */\r\nexport default function Dialog({\r\n open,\r\n onClose,\r\n title,\r\n subtitle,\r\n footer,\r\n size = 'md',\r\n height = 'auto',\r\n closeOnBackdrop = true,\r\n closeLabel = 'Fechar',\r\n flush = false,\r\n children,\r\n}: DialogProps) {\r\n const [root, setRoot] = useState<HTMLDivElement | null>(null)\r\n const janela = useRef<HTMLDivElement>(null)\r\n const fundo = useRef<HTMLDivElement>(null)\r\n const titleId = useId()\r\n const depth = useContext(DialogDepthContext)\r\n const fechar = useRef(onClose)\r\n fechar.current = onClose\r\n // Clique fora: só conta se começou e acabou no fundo (arrastar a partir de um\r\n // campo para fora não fecha a janela).\r\n const pressedBackdrop = useRef(false)\r\n\r\n useEffect(() => {\r\n if (!open || !root) return\r\n const anterior = document.activeElement as HTMLElement | null\r\n const aberta = { root, janela: janela.current, depth }\r\n empilhar(aberta)\r\n silenciar()\r\n const topo = doTopo()\r\n const dentro = topo.janela?.contains(document.activeElement)\r\n if (!dentro) topo.janela?.focus()\r\n\r\n const onKeyDown = (e: KeyboardEvent) => {\r\n if (e.key !== 'Escape' || e.defaultPrevented) return\r\n if (doTopo() !== aberta) return\r\n fechar.current()\r\n }\r\n document.addEventListener('keydown', onKeyDown)\r\n\r\n return () => {\r\n document.removeEventListener('keydown', onKeyDown)\r\n abertas.splice(abertas.indexOf(aberta), 1)\r\n silenciar()\r\n const voltar = anterior && !anterior.closest('[inert]')\r\n if (voltar) anterior.focus()\r\n else doTopo()?.janela?.focus()\r\n }\r\n }, [open, root, depth])\r\n\r\n // A página por trás não faz scroll enquanto a janela está aberta.\r\n useEffect(() => {\r\n if (!open) return\r\n const html = document.documentElement\r\n const before = html.style.overflow\r\n html.style.overflow = 'hidden'\r\n return () => {\r\n html.style.overflow = before\r\n }\r\n }, [open])\r\n\r\n const noFundo = (e: React.SyntheticEvent) => e.target === fundo.current || e.target === e.currentTarget\r\n\r\n return createPortal(\r\n <div\r\n ref={setRoot}\r\n hidden={!open}\r\n onPointerDown={(e) => {\r\n pressedBackdrop.current = noFundo(e)\r\n }}\r\n onClick={(e) => {\r\n if (closeOnBackdrop && pressedBackdrop.current && noFundo(e)) onClose()\r\n }}\r\n className={`fixed inset-0 z-[200] items-center justify-center p-dialog-margin ${open ? 'flex' : 'hidden'}`}\r\n >\r\n <div ref={fundo} className=\"absolute inset-0 bg-black/40 motion-safe:animate-backdrop-in\" />\r\n <div\r\n ref={janela}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby={titleId}\r\n tabIndex={-1}\r\n className={\r\n `relative flex max-h-full w-full ${WIDTHS[size]} flex-col overflow-hidden rounded-2xl bg-card text-foreground shadow-modal outline-none ` +\r\n `motion-safe:animate-dialog-in ${height === 'full' ? 'h-full' : ''}`\r\n }\r\n >\r\n <header className=\"flex flex-none items-center justify-between gap-3 bg-gradient-to-br from-primary to-primary-strong px-5 py-3.5\">\r\n <div className=\"min-w-0\">\r\n <h2 id={titleId} className=\"m-0 truncate text-title font-bold text-primary-foreground\">\r\n {title}\r\n </h2>\r\n {subtitle && <p className=\"m-0 mt-0.5 truncate text-caption text-primary-foreground/75\">{subtitle}</p>}\r\n </div>\r\n <button\r\n type=\"button\"\r\n aria-label={closeLabel}\r\n title={closeLabel}\r\n onClick={onClose}\r\n className=\"flex h-7 w-7 flex-none cursor-pointer items-center justify-center rounded-md border border-primary-foreground/25 bg-primary-foreground/15 text-base leading-none text-primary-foreground transition-colors hover:bg-primary-foreground/25 focus-visible:ring-2 focus-visible:ring-primary-foreground/60 focus-visible:outline-none\"\r\n >\r\n ×\r\n </button>\r\n </header>\r\n\r\n <DialogContainerContext.Provider value={root}>\r\n <DialogDepthContext.Provider value={depth + 1}>\r\n <div className={flush ? 'flex min-h-0 flex-1 flex-col overflow-hidden bg-canvas' : 'min-h-0 flex-1 overflow-y-auto bg-canvas px-5 py-4'}>{children}</div>\r\n\r\n {footer && <footer className=\"flex flex-none flex-wrap justify-end gap-2 border-t border-border bg-muted/40 px-5 py-[18px]\">{footer}</footer>}\r\n </DialogDepthContext.Provider>\r\n </DialogContainerContext.Provider>\r\n </div>\r\n </div>,\r\n document.body,\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Dialog.js","sources":["../../../../../../src/v1/components/overlays/Dialog/Dialog.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useId, useRef, useState } from 'react'\r\nimport { createPortal } from 'react-dom'\r\n\r\nconst DialogContainerContext = createContext<HTMLElement | null>(null)\r\nconst DialogDepthContext = createContext(0)\r\n\r\n/**\r\n * O elemento da janela, para usar como destino de `createPortal` (ex: avisos,\r\n * confirmações, sobreposições) quando o conteúdo deve ficar junto dela. Um\r\n * portal para `document.body` também funciona, desde que fique por cima\r\n * (`z-index` acima de 200). Fora de um Dialog devolve `null`.\r\n *\r\n * `createPortal(conteudo, useDialogContainer() ?? document.body)`\r\n */\r\nexport const useDialogContainer = () => useContext(DialogContainerContext)\r\n\r\nexport interface DialogProps {\r\n /** Aberta ou fechada. A app guarda este estado. */\r\n open: boolean\r\n /** Pedido para fechar (×, Esc ou clique fora): a app põe `open` a `false`. */\r\n onClose: () => void\r\n /** Normalmente texto; aceita JSX (ex: um nome em negrito). */\r\n title: React.ReactNode\r\n /** Linha por baixo do título (ex: nome · departamento). */\r\n subtitle?: React.ReactNode\r\n /** Botões no rodapé, sempre visíveis (ex: Confirmar, Fechar). */\r\n footer?: React.ReactNode\r\n /**\r\n * Largura máxima: `sm` 480px, `md` 720px, `lg` 1000px, `xl` até 1600px (95% do ecrã, para janelas com\r\n * mapa e tabela lado a lado). Nunca passa o ecrã.\r\n */\r\n size?: 'sm' | 'md' | 'lg' | 'xl'\r\n /**\r\n * Altura: `auto` (por omissão) cresce com o conteúdo; `full` ocupa sempre a\r\n * altura disponível (útil quando o conteúdo muda de tamanho e a janela não\r\n * deve saltar). Em ambos, se o conteúdo não couber, o corpo faz scroll.\r\n */\r\n height?: 'auto' | 'full'\r\n /** Fechar ao clicar fora da janela. Por omissão sim; num formulário longo pode valer a pena desligar. */\r\n closeOnBackdrop?: boolean\r\n /** Nome do botão ×, para leitores de ecrã. Por omissão: \"Fechar\". */\r\n closeLabel?: string\r\n /**\r\n * Corpo sem margens e sem scroll: o conteúdo ocupa-o todo e gere o seu próprio\r\n * scroll (ex: passos que deslizam, rodapé próprio de cada passo).\r\n */\r\n flush?: boolean\r\n children: React.ReactNode\r\n}\r\n\r\nconst WIDTHS = { sm: 'max-w-dialog-sm', md: 'max-w-dialog-md', lg: 'max-w-dialog-lg', xl: 'max-w-dialog-xl' }\r\n\r\ninterface Aberta {\r\n root: HTMLElement\r\n janela: HTMLElement | null\r\n depth: number\r\n}\r\n\r\nconst abertas: Aberta[] = []\r\nconst silenciados = new Set<Element>()\r\n\r\nconst doTopo = () => abertas[abertas.length - 1]\r\n\r\nfunction silenciar() {\r\n for (const el of silenciados) el.removeAttribute('inert')\r\n silenciados.clear()\r\n const topo = doTopo()\r\n if (!topo) return\r\n for (const el of Array.from(document.body.children)) {\r\n if (el === topo.root || el.hasAttribute('inert')) continue\r\n el.setAttribute('inert', '')\r\n silenciados.add(el)\r\n }\r\n}\r\n\r\n// Uma janela aberta dentro de outra fica sempre por cima dela, mesmo que abram juntas.\r\nfunction empilhar(aberta: Aberta) {\r\n const acima = abertas.findIndex((a) => a.depth > aberta.depth)\r\n if (acima === -1) abertas.push(aberta)\r\n else abertas.splice(acima, 0, aberta)\r\n}\r\n\r\n/**\r\n * Janela por cima da página: cabeçalho navy com título, corpo que faz scroll e\r\n * rodapé com os botões ([Button](?path=/docs/v1-primitives-buttons-button--docs)).\r\n *\r\n * É a janela modal da V1 (substitui o `Modal` legacy).\r\n *\r\n * Fundo escurecido, Esc para fechar e, ao fechar, o foco volta ao sítio onde\r\n * estava. Enquanto está aberta, o resto da página fica\r\n * [inert](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/inert):\r\n * nem rato, nem teclado, nem leitores de ecrã lhe chegam. Os portais abertos\r\n * depois dela (seletores, confirmações) continuam a responder.\r\n *\r\n * O conteúdo fica montado mesmo com a janela fechada, para poder carregar antes\r\n * de ela abrir.\r\n *\r\n * O tamanho adapta-se ao ecrã só com CSS: nunca passa a largura nem a altura\r\n * da janela do browser (com uma margem), e acompanha-a quando é\r\n * redimensionada. O que não couber faz scroll no corpo; o cabeçalho e o\r\n * rodapé ficam sempre visíveis.\r\n *\r\n * **Exemplo completo:** [Formulário em modal](?path=/docs/v1-patterns-forms-formulário-em-modal--docs).\r\n */\r\nexport default function Dialog({\r\n open,\r\n onClose,\r\n title,\r\n subtitle,\r\n footer,\r\n size = 'md',\r\n height = 'auto',\r\n closeOnBackdrop = true,\r\n closeLabel = 'Fechar',\r\n flush = false,\r\n children,\r\n}: DialogProps) {\r\n const [root, setRoot] = useState<HTMLDivElement | null>(null)\r\n const janela = useRef<HTMLDivElement>(null)\r\n const fundo = useRef<HTMLDivElement>(null)\r\n const titleId = useId()\r\n const depth = useContext(DialogDepthContext)\r\n const fechar = useRef(onClose)\r\n fechar.current = onClose\r\n // Clique fora: só conta se começou e acabou no fundo (arrastar a partir de um\r\n // campo para fora não fecha a janela).\r\n const pressedBackdrop = useRef(false)\r\n\r\n useEffect(() => {\r\n if (!open || !root) return\r\n const anterior = document.activeElement as HTMLElement | null\r\n const aberta = { root, janela: janela.current, depth }\r\n empilhar(aberta)\r\n silenciar()\r\n const topo = doTopo()\r\n const dentro = topo.janela?.contains(document.activeElement)\r\n if (!dentro) topo.janela?.focus()\r\n\r\n const onKeyDown = (e: KeyboardEvent) => {\r\n if (e.key !== 'Escape' || e.defaultPrevented) return\r\n if (doTopo() !== aberta) return\r\n fechar.current()\r\n }\r\n document.addEventListener('keydown', onKeyDown)\r\n\r\n return () => {\r\n document.removeEventListener('keydown', onKeyDown)\r\n abertas.splice(abertas.indexOf(aberta), 1)\r\n silenciar()\r\n const voltar = anterior && !anterior.closest('[inert]')\r\n if (voltar) anterior.focus()\r\n else doTopo()?.janela?.focus()\r\n }\r\n }, [open, root, depth])\r\n\r\n // A página por trás não faz scroll enquanto a janela está aberta.\r\n useEffect(() => {\r\n if (!open) return\r\n const html = document.documentElement\r\n const before = html.style.overflow\r\n html.style.overflow = 'hidden'\r\n return () => {\r\n html.style.overflow = before\r\n }\r\n }, [open])\r\n\r\n const noFundo = (e: React.SyntheticEvent) => e.target === fundo.current || e.target === e.currentTarget\r\n\r\n return createPortal(\r\n <div\r\n ref={setRoot}\r\n hidden={!open}\r\n onPointerDown={(e) => {\r\n pressedBackdrop.current = noFundo(e)\r\n }}\r\n onClick={(e) => {\r\n if (closeOnBackdrop && pressedBackdrop.current && noFundo(e)) onClose()\r\n }}\r\n className={`fixed inset-0 z-[200] items-center justify-center p-dialog-margin ${open ? 'flex' : 'hidden'}`}\r\n >\r\n <div ref={fundo} className=\"absolute inset-0 bg-black/40 motion-safe:animate-backdrop-in\" />\r\n <div\r\n ref={janela}\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-labelledby={titleId}\r\n tabIndex={-1}\r\n className={\r\n // No escuro a sombra não se vê: a linha (a mesma dos menus) separa a janela do fundo.\r\n `relative flex max-h-full w-full ${WIDTHS[size]} flex-col overflow-hidden rounded-2xl bg-card text-foreground shadow-modal outline-none dark:border dark:border-slate-700 ` +\r\n `motion-safe:animate-dialog-in ${height === 'full' ? 'h-full' : ''}`\r\n }\r\n >\r\n <header className=\"flex flex-none items-center justify-between gap-3 bg-gradient-to-br from-primary to-primary-strong px-5 py-3.5\">\r\n <div className=\"min-w-0\">\r\n <h2 id={titleId} className=\"m-0 truncate text-title font-bold text-primary-foreground\">\r\n {title}\r\n </h2>\r\n {subtitle && <p className=\"m-0 mt-0.5 truncate text-caption text-primary-foreground/75\">{subtitle}</p>}\r\n </div>\r\n <button\r\n type=\"button\"\r\n aria-label={closeLabel}\r\n title={closeLabel}\r\n onClick={onClose}\r\n className=\"flex h-7 w-7 flex-none cursor-pointer items-center justify-center rounded-md border border-primary-foreground/25 bg-primary-foreground/15 text-base leading-none text-primary-foreground transition-colors hover:bg-primary-foreground/25 focus-visible:ring-2 focus-visible:ring-primary-foreground/60 focus-visible:outline-none\"\r\n >\r\n ×\r\n </button>\r\n </header>\r\n\r\n <DialogContainerContext.Provider value={root}>\r\n <DialogDepthContext.Provider value={depth + 1}>\r\n <div className={flush ? 'flex min-h-0 flex-1 flex-col overflow-hidden bg-canvas' : 'min-h-0 flex-1 overflow-y-auto bg-canvas px-5 py-4'}>{children}</div>\r\n\r\n {footer && <footer className=\"flex flex-none flex-wrap justify-end gap-2 border-t border-border bg-muted/40 px-5 py-[18px]\">{footer}</footer>}\r\n </DialogDepthContext.Provider>\r\n </DialogContainerContext.Provider>\r\n </div>\r\n </div>,\r\n document.body,\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
|
2
2
|
import { useRef, useId, useState, useMemo, useEffect } from "react";
|
|
3
3
|
import { createPortal } from "react-dom";
|
|
4
4
|
import { SlidersIcon, ListIcon, ChevronUpIcon, ChevronDownIcon, CheckIcon } from "../../../foundations/icons/icons.js";
|
|
5
|
+
import { useDialogContainer } from "../../overlays/Dialog/Dialog.js";
|
|
5
6
|
const ALTURA = 420;
|
|
6
7
|
const LARGURA = 320;
|
|
7
8
|
function ColumnFilter({
|
|
@@ -24,6 +25,7 @@ function ColumnFilter({
|
|
|
24
25
|
onFechar
|
|
25
26
|
}) {
|
|
26
27
|
const caixa = useRef(null);
|
|
28
|
+
const dialogContainer = useDialogContainer();
|
|
27
29
|
const idMin = useId();
|
|
28
30
|
const [busca, setBusca] = useState("");
|
|
29
31
|
const [rascunho, setRascunho] = useState(() => new Set(seleccionados));
|
|
@@ -283,7 +285,7 @@ function ColumnFilter({
|
|
|
283
285
|
]
|
|
284
286
|
}
|
|
285
287
|
),
|
|
286
|
-
document.body
|
|
288
|
+
dialogContainer ?? document.body
|
|
287
289
|
);
|
|
288
290
|
}
|
|
289
291
|
export {
|