@nextelco/common-ui 2.1.100 → 2.1.101
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/legacy/components/molecules/DataTable/DataTable.js +6 -0
- package/dist/esm/legacy/components/molecules/DataTable/DataTable.js.map +1 -1
- package/dist/esm/v1/components/overlays/Dialog/Dialog.js +1 -0
- package/dist/esm/v1/components/overlays/Dialog/Dialog.js.map +1 -1
- package/dist/types/legacy/components/molecules/DataTable/DataTable.d.ts +6 -1
- package/package.json +1 -1
|
@@ -127,6 +127,7 @@ function DataTable({
|
|
|
127
127
|
onRowClick,
|
|
128
128
|
onRowDoubleClick,
|
|
129
129
|
rowClassName,
|
|
130
|
+
onVisibleRowsChange,
|
|
130
131
|
isLoading,
|
|
131
132
|
isError,
|
|
132
133
|
maxHeight = "70vh",
|
|
@@ -400,6 +401,11 @@ function DataTable({
|
|
|
400
401
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
401
402
|
[filtered, sort, columns]
|
|
402
403
|
);
|
|
404
|
+
const avisarVisiveis = useRef(onVisibleRowsChange);
|
|
405
|
+
avisarVisiveis.current = onVisibleRowsChange;
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
avisarVisiveis.current?.(sorted);
|
|
408
|
+
}, [sorted]);
|
|
403
409
|
const alternarGrupo = (key) => {
|
|
404
410
|
if (!allData) onDataNeeded?.();
|
|
405
411
|
setGroupByKeys((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.js","sources":["../../../../../../src/legacy/components/molecules/DataTable/DataTable.tsx"],"sourcesContent":["import React, { Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'\r\nimport Escolha from '../../ui/Escolha'\r\nimport { createPortal } from 'react-dom'\r\n\r\n// ─── Tipos ──────────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface DataTableColumn<T> {\r\n key: string\r\n label: string\r\n /** Valor usado p/ ordenar, filtrar e exportar. Sem isto usa-se row[key]. */\r\n value?: (row: T) => string | number | null | undefined\r\n /** Conteúdo apresentado. Sem isto mostra o value. */\r\n render?: (row: T) => React.ReactNode\r\n sortType?: 'text' | 'number' | 'date'\r\n /**\r\n * Fixa a coluna no scroll horizontal (position: sticky).\r\n * `true` ou `'left'` prende à esquerda; `'right'` prende à direita — útil para\r\n * a coluna de ações, que de outro modo fica fora de vista (e com os botões\r\n * cortados) assim que a tabela e mais larga do que o espaço disponível.\r\n */\r\n sticky?: boolean | 'left' | 'right'\r\n align?: 'left' | 'right' | 'center'\r\n /** minWidth em px. */\r\n width?: number\r\n /** Largura máxima em px (o conteúdo corta com \"…\" e mostra o valor completo no tooltip). Sobrepõe-se a `defaultMaxColumnWidth`. */\r\n maxWidth?: number\r\n /**\r\n * Filtro por coluna (distinct dos valores, multi-seleção; intervalo nas colunas number/date).\r\n * Por omissão LIGADO em todas as colunas com dados — `false` desliga. Colunas sem dados (ex.: ações) não o mostram.\r\n */\r\n filterable?: boolean\r\n /** Desliga a ordenação nesta coluna (default: ordenável). */\r\n sortable?: boolean\r\n /**\r\n * Agrupar por esta coluna. Por omissão LIGADO nas colunas de texto com dados e DESLIGADO nas de valor contínuo\r\n * (`sortType` number/date, ou com `groupAggregate`), onde cada linha daria o seu próprio grupo. `true`/`false` forçam.\r\n */\r\n groupable?: boolean\r\n /** Chave usada só para ORDENAR os grupos (sobrepõe-se a `value` ao agrupar por esta coluna). */\r\n groupSortKey?: (row: T) => string | number | null | undefined\r\n /**\r\n * Filtros extra desenhados como funis adicionais no cabeçalho desta coluna — para filtrar\r\n * por um campo que não tem coluna própria (ex: filtrar a coluna \"Obra\" por concelho).\r\n */\r\n extraFilters?: { key: string; label: string; value: (row: T) => string | number | null | undefined }[]\r\n headerClassName?: string\r\n cellClassName?: string\r\n /** Conteúdo extra por baixo do título da coluna (ex: somatório). Recebe as linhas já filtradas. */\r\n headerExtra?: (rows: T[]) => React.ReactNode\r\n /**\r\n * Valor desta coluna na linha de cada grupo (ex: soma em €), alinhado com a coluna. Recebe as linhas do grupo\r\n * (já filtradas). Também é usado na linha de total (`totalRow`), com todas as linhas filtradas.\r\n */\r\n groupAggregate?: (rows: T[]) => React.ReactNode\r\n /**\r\n * Cores por valor para os chips de filtro ativo (valor → classes tailwind). Com isto, cada\r\n * valor escolhido ganha o seu chip, removível à parte.\r\n */\r\n valueChipColors?: Record<string, string>\r\n}\r\n\r\n/**\r\n * Ordenação, filtros e agrupamento do cabeçalho vivem dentro da tabela. Quem está por fora\r\n * (ex: exportar para Excel, guardar uma vista) precisa deles — daí este handle, preenchido\r\n * em `viewRef`.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface DataTableView<T> {\r\n /** Aplica ordenação + filtros de coluna a qualquer lista (ex: ao dataset completo). */\r\n apply: (rows: T[]) => T[]\r\n /** Linhas à vista, já filtradas e ordenadas (todas as páginas). */\r\n rows: T[]\r\n hasFilters: boolean\r\n /**\r\n * Há filtros, ordenação ou agrupamento ativos — tudo do lado do cliente. Quem alimenta a\r\n * tabela usa isto para não largar o dataset completo: sem ele, filtrar sobre a página dá\r\n * uma lista vazia com o total nos milhares.\r\n */\r\n precisaDataset: boolean\r\n /** Estado guardável da vista. */\r\n estado: () => DataTableEstado\r\n /** Repõe um estado guardado (ex: ao aplicar uma vista do utilizador). */\r\n repor: (e: DataTableEstado | undefined) => void\r\n}\r\n\r\n/**\r\n * O que uma vista guardada leva da tabela. Sets viram arrays porque isto é serializado\r\n * para JSON.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface DataTableEstado {\r\n columnFilters?: Record<string, string[]>\r\n rangeFilters?: Record<string, { min?: number; max?: number }>\r\n sortKey?: string | null\r\n sortDir?: 'asc' | 'desc'\r\n /** Colunas por que se agrupa, pela ordem dos níveis. */\r\n groupBy?: string[]\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface DataTableLabels {\r\n /** {count} é substituído pelo total (após filtros). */\r\n results: string\r\n /** Tooltip da pega de redimensionar coluna. */\r\n resizeColumn?: string\r\n export: string\r\n loading: string\r\n empty: string\r\n error: string\r\n search: string\r\n clear: string\r\n clearAll: string\r\n noOptions: string\r\n /** {count} = nº de valores selecionados (quando > 2, mostra \"N sel.\"). */\r\n selected: string\r\n /** {shown}/{total} — aparece quando o distinct da coluna tem mais valores do que os mostrados. */\r\n moreOptions: string\r\n /** Aviso no popup enquanto `allData` não chegou — as opções ainda são só as de `data`. */\r\n loadingOptions: string\r\n /** Placeholders dos dois campos do filtro por intervalo (colunas numéricas e de datas). */\r\n min: string\r\n max: string\r\n /** {page}/{pages} são substituídos. */\r\n page: string\r\n prev: string\r\n next: string\r\n /** Ordenação a partir do popup de filtro. */\r\n sortAsc: string\r\n sortDesc: string\r\n /** Agrupamento. {label} é o nome da coluna. */\r\n groupBy: string\r\n ungroup: string\r\n clearGroups: string\r\n /** Selector de linhas por página (só com `pageSizeOptions`). */\r\n perPage: string\r\n /** `filterMode: 'excel'`: primeira linha da lista de valores, o \"(Selecionar tudo)\" do Excel. */\r\n selectAll: string\r\n /** O mesmo, com pesquisa activa — aí só abrange os valores que correspondem. */\r\n selectAllResults: string\r\n /** Rótulo da linha de total (`totalRow`). */\r\n total: string\r\n}\r\n\r\nconst DEFAULT_LABELS: DataTableLabels = {\r\n total: 'Total',\r\n results: '{count} results',\r\n resizeColumn: 'Drag to resize · double-click to reset',\r\n export: 'Export',\r\n loading: 'Loading…',\r\n empty: 'No data',\r\n error: 'Failed to load',\r\n search: 'Search…',\r\n clear: 'Clear',\r\n clearAll: 'Clear all',\r\n noOptions: 'No options',\r\n selected: '{count} sel.',\r\n moreOptions: 'Showing {shown} of {total} — refine your search',\r\n loadingOptions: 'Loading options from all pages…',\r\n min: 'From',\r\n max: 'To',\r\n page: 'Page {page}/{pages}',\r\n prev: 'Prev',\r\n next: 'Next',\r\n sortAsc: 'Sort ascending',\r\n sortDesc: 'Sort descending',\r\n groupBy: 'Group by {label}',\r\n ungroup: 'Remove grouping by {label}',\r\n clearGroups: 'Clear grouping',\r\n perPage: 'Per page',\r\n selectAll: '(Select all)',\r\n selectAllResults: '(Select results)',\r\n}\r\n\r\n// ─── Ícones (inline, sem dependências) ───────────────────────────────────────\r\n\r\nconst Funnel = ({ size = 12, className }: { size?: number; className?: string }) => (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={className}>\r\n <path d=\"M22 3H2l8 9.46V19l4 2v-8.54L22 3z\" />\r\n </svg>\r\n)\r\n\r\nconst Layers = ({ size = 11, className }: { size?: number; className?: string }) => (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={className}>\r\n <path d=\"m12 2 9 5-9 5-9-5 9-5z\" />\r\n <path d=\"m3 17 9 5 9-5M3 12l9 5 9-5\" />\r\n </svg>\r\n)\r\n\r\nconst Caret = ({ size = 11, className }: { size?: number; className?: string }) => (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={className}>\r\n <path d=\"m9 18 6-6-6-6\" />\r\n </svg>\r\n)\r\n\r\ninterface DataTableProps<T> {\r\n columns: DataTableColumn<T>[]\r\n data: T[]\r\n /**\r\n * Dataset completo, para o distinct dos filtros e para filtrar/ordenar/agrupar sobre ele.\r\n *\r\n * Sem isto a tabela só conhece `data` — e numa app que pagina no servidor isso são as\r\n * linhas da página, pelo que o funil de cada coluna listava só esses valores (e filtrar\r\n * por um valor que só existe noutra página dava zero linhas).\r\n */\r\n allData?: T[]\r\n /**\r\n * Chamado quando a tabela precisa de `allData` e ainda não o tem: ao abrir um funil, ao\r\n * ordenar, ao agrupar, ou quando já há filtros ativos. É o sinal para a app ir buscar o\r\n * dataset completo — uma vez, e só se alguém mexer.\r\n */\r\n onDataNeeded?: () => void\r\n /** `allData` a caminho — mostra um aviso no popup em vez de opções silenciosamente incompletas. */\r\n isAllDataLoading?: boolean\r\n /** Força o modo cliente mesmo sem interação (desde que `allData` exista). */\r\n alwaysClient?: boolean\r\n /**\r\n * Avisa quando a tabela passa (ou deixa) de trabalhar sobre `allData`. Quem tem paginação\r\n * de servidor por fora usa isto para a esconder: em modo cliente as linhas à vista já não\r\n * são a página que esse controlo diz estar a mostrar.\r\n */\r\n onClientModeChange?: (active: boolean) => void\r\n /** Chave estável por linha. Obrigatória para a seleção; sem ela usa-se o índice. */\r\n rowKey?: (row: T, index: number) => string | number\r\n onRowClick?: (row: T) => void\r\n /** Duplo clique — atalho para o detalhe sem roubar o clique simples (seleção, edição inline). */\r\n onRowDoubleClick?: (row: T) => void\r\n /**\r\n * Classes extra por linha, a partir dos dados — estado de risco, linha desactivada, etc.\r\n * Vem depois das classes da tabela, por isso ganha em caso de conflito.\r\n */\r\n rowClassName?: (row: T) => string | undefined\r\n isLoading?: boolean\r\n isError?: boolean\r\n /** max-height do viewport com scroll (default 70vh). */\r\n maxHeight?: string\r\n /** Linhas por página. Sem isto, mostra tudo (pode ser lento com muitas linhas). */\r\n pageSize?: number\r\n /** Mostra um selector de linhas por página com estas opções. */\r\n pageSizeOptions?: number[]\r\n /** Caixa de seleção por linha + no cabeçalho. */\r\n selectable?: boolean\r\n onSelectionChange?: (selectedKeys: Set<string>) => void\r\n /** Mudar este número limpa a seleção (ex: depois de uma ação em massa). */\r\n clearSelectionTrigger?: number\r\n /** Guarda filtros e intervalos no sessionStorage com esta chave (e as larguras das colunas no localStorage). */\r\n storageKey?: string\r\n /** Largura máxima por defeito das colunas, em px (default 360). `0` desliga o limite. */\r\n defaultMaxColumnWidth?: number\r\n /** Colunas redimensionáveis arrastando o limite do cabeçalho; duplo clique repõe (default true). */\r\n resizableColumns?: boolean\r\n /**\r\n * \"Ordenar A→Z / Z→A\" no topo do popup de filtro. Ligado por omissão (o cabeçalho é o mesmo em todos os\r\n * portais); `false` desliga. Os textos vêm de `labels.sortAsc`/`sortDesc` (por omissão em inglês).\r\n */\r\n sortFromFilter?: boolean\r\n /**\r\n * Semântica do filtro por coluna.\r\n *\r\n * - `'simple'`: abre com tudo **desmarcado** e marcar um valor quer dizer \"só este\".\r\n * Conjunto vazio = sem filtro.\r\n * - `'excel'` (default): abre com tudo **marcado** e desmarca-se o que se quer excluir, com uma linha\r\n * `(Selecionar tudo)` no topo e curingas (`*`, `?`, `~`) na pesquisa. É o que a folha de\r\n * cálculo faz, e o que quem vem de lá espera. Conjunto vazio = **nada seleccionado**, e a\r\n * tabela fica sem linhas.\r\n *\r\n * No modo Excel o distinto da coluna aberta deixa de respeitar os filtros das outras: o que a\r\n * lista mostra tem de ser exactamente o que a selecção materializa, senão marcar \"tudo\"\r\n * apagaria em silêncio os valores que outra coluna escondeu.\r\n */\r\n filterMode?: 'simple' | 'excel'\r\n /** Ordenação inicial. */\r\n defaultSortKey?: string\r\n defaultSortDir?: 'asc' | 'desc'\r\n /** Recebe o estado da vista (ordenação + filtros + agrupamento) para uso externo. */\r\n viewRef?: React.MutableRefObject<DataTableView<T> | null>\r\n /** Mostra o botão de exportar. Sem `onExport`, faz download CSV nativo. */\r\n exportFileName?: string\r\n /** Export custom (ex: XLSX na app). Recebe as linhas filtradas + ordenadas. */\r\n onExport?: (rows: T[]) => void\r\n /** Conteúdo extra na toolbar (à direita, antes do export). */\r\n extraToolbar?: React.ReactNode\r\n /** Texto por baixo do estado vazio. */\r\n emptySubLabel?: string\r\n /** Ilustração do estado vazio. */\r\n emptyIcon?: React.ReactNode\r\n labels?: Partial<DataTableLabels>\r\n /** Linha de total no fim (fica fixa em baixo), com o `groupAggregate` de cada coluna sobre as linhas filtradas. */\r\n totalRow?: boolean\r\n /** Agrupamento inicial (chaves de colunas, por ordem de nível). A pessoa pode mudá-lo depois. */\r\n defaultGroupBy?: string[]\r\n}\r\n\r\n// ─── Helpers ─────────────────────────────────────────────────────────────────\r\n\r\nconst EMPTY = '—'\r\n\r\n/** Nº máximo de opções renderizadas no popup de filtro por coluna — sem isto, uma coluna\r\n * quase-única por linha (ex: um ID) põe milhares de `<button>` no DOM de cada vez que o popup\r\n * abre, mesmo só visualmente cortado pelo scroll. A pesquisa continua a correr sobre o distinct\r\n * completo (memoizado), só o que é DESENHADO fica limitado. */\r\nconst MAX_OPCOES_FILTRO = 50\r\n\r\n/** Largura assumida p/ acumular o offset de colunas sticky sem `width` definido. */\r\nconst DEFAULT_STICKY_WIDTH = 160\r\n\r\n/** Collator único, reutilizado em todas as comparações de texto.\r\n * `a.localeCompare(b, undefined, { numeric: true })` constrói um `Intl.Collator` **a cada\r\n * chamada**; num sort de milhares de valores (ex: o distinct de uma coluna de IDs, com ~12k\r\n * valores ≈ 150k comparações) era isso, e não o render, que fazia o popup demorar a abrir. */\r\nconst COLLATOR = new Intl.Collator(undefined, { numeric: true })\r\n\r\nfunction rawValue<T>(row: T, col: DataTableColumn<T>): string | number | null {\r\n if (col.value) {\r\n const v = col.value(row)\r\n return v == null ? null : v\r\n }\r\n const v = (row as Record<string, unknown>)[col.key]\r\n if (v == null) return null\r\n if (typeof v === 'number' || typeof v === 'string') return v\r\n return String(v)\r\n}\r\n\r\n/** Valor normalizado p/ filtro (string; vazio → EMPTY). */\r\nfunction filterKey<T>(row: T, col: DataTableColumn<T>): string {\r\n const v = rawValue(row, col)\r\n return v == null || v === '' ? EMPTY : String(v)\r\n}\r\n\r\n/**\r\n * Timestamp de um valor de coluna de data. As apps passam a data já formatada (`value:\r\n * dataPt(...)` → \"14/08/2026\"), e `new Date(\"14/08/2026\")` devolve `Invalid Date` — ou, pior,\r\n * lê \"06/03/2026\" como 3 de junho, à americana. Daí o parse explícito de dd/mm/aaaa antes do\r\n * fallback para o `Date` nativo (que trata do ISO).\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function timestampDeData(v: unknown): number {\r\n if (v == null || v === '') return NaN\r\n if (v instanceof Date) return v.getTime()\r\n const s = String(v).trim()\r\n const m = /^(\\d{1,2})[/-](\\d{1,2})[/-](\\d{4})/.exec(s)\r\n if (m) return Date.UTC(Number(m[3]), Number(m[2]) - 1, Number(m[1]))\r\n const t = new Date(s).getTime()\r\n return Number.isNaN(t) ? NaN : t\r\n}\r\n\r\nfunction compare(\r\n a: string | number | null,\r\n b: string | number | null,\r\n type: DataTableColumn<unknown>['sortType']\r\n): number {\r\n if (a == null && b == null) return 0\r\n if (a == null) return 1\r\n if (b == null) return -1\r\n if (type === 'number') return Number(a) - Number(b)\r\n if (type === 'date') return timestampDeData(a) - timestampDeData(b)\r\n return COLLATOR.compare(String(a), String(b))\r\n}\r\n\r\n/** Valor comparável de uma célula para o filtro por intervalo (número ou data). */\r\nfunction valorParaIntervalo<T>(row: T, col: DataTableColumn<T>): number {\r\n const v = rawValue(row, col)\r\n return col.sortType === 'date' ? timestampDeData(v) : Number(v)\r\n}\r\n\r\nfunction toCsv<T>(columns: DataTableColumn<T>[], rows: T[]): string {\r\n const esc = (v: unknown) => {\r\n const s = v == null ? '' : String(v)\r\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s\r\n }\r\n const head = columns.map((c) => esc(c.label)).join(',')\r\n const body = rows.map((r) => columns.map((c) => esc(rawValue(r, c))).join(',')).join('\\n')\r\n return `${head}\\n${body}`\r\n}\r\n\r\nconst ordenarRotulos = (a: string, b: string) => (a === EMPTY ? 1 : b === EMPTY ? -1 : COLLATOR.compare(a, b))\r\n\r\n/** Identidade estável para as dependências que o modo Excel não usa — ver `cruzFiltros`. */\r\nconst SEM_CRUZAMENTO: [string, never][] = []\r\n\r\nconst escaparRegex = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n\r\n/**\r\n * Predicado da caixa de pesquisa do filtro por coluna, com os curingas do Excel.\r\n *\r\n * `*` = qualquer sequência, `?` = exactamente um carácter, `~` escapa qualquer um dos três. Sem\r\n * curinga nenhum é \"contém\"; **com** curinga o padrão passa a ser comparado com o valor **inteiro**,\r\n * e é daí que sai o \"começa por\" (`25VCT*`), o \"termina em\" (`*.fbo`) e o \"tem isto no meio\"\r\n * (`*RA2*`).\r\n *\r\n * No Excel esse ancorar vive nos Filtros de Texto (Começa por/Termina em) e não na caixa de\r\n * pesquisa, que é sempre \"contém\" — juntar os dois na caixa evita ter de trazer também o submenu, e\r\n * sem curingas o comportamento é o mesmo dos dois lados.\r\n *\r\n * Tudo em minúsculas: quem chama passa a pesquisa e o valor já em minúsculas.\r\n */\r\nexport function pesquisaCuringa(q: string): (v: string) => boolean {\r\n let fonte = ''\r\n let literal = ''\r\n let temCuringa = false\r\n for (let i = 0; i < q.length; i++) {\r\n const c = q[i]!\r\n const seguinte = q[i + 1]\r\n if (c === '~' && (seguinte === '*' || seguinte === '?' || seguinte === '~')) {\r\n fonte += escaparRegex(seguinte)\r\n literal += seguinte\r\n i++\r\n continue\r\n }\r\n if (c === '*' || c === '?') {\r\n fonte += c === '*' ? '.*' : '.'\r\n literal += c\r\n temCuringa = true\r\n continue\r\n }\r\n fonte += escaparRegex(c)\r\n literal += c\r\n }\r\n // Sem curinga usa-se o literal (e não `q`), para `~*` procurar mesmo um asterisco.\r\n if (!temCuringa) return (v) => v.includes(literal)\r\n const rx = new RegExp(`^${fonte}$`)\r\n return (v) => rx.test(v)\r\n}\r\n\r\ninterface GrupoNo<T> {\r\n val: string\r\n ordem: string\r\n path: string\r\n items: T[]\r\n filhos: GrupoNo<T>[]\r\n}\r\n\r\n// ─── Componente ──────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Tabela genérica: ordenação, filtro por coluna com distinct (multi-seleção) e por intervalo,\r\n * agrupamento multi-nível, seleção de linhas, colunas fixas (sticky), align/width, export\r\n * (CSV nativo ou custom) e estados loading/vazio/erro. Sem dependências de ícones/i18n —\r\n * textos via `labels`.\r\n *\r\n * Com paginação de servidor, passa também `allData`/`onDataNeeded`: sem o dataset completo,\r\n * filtrar, ordenar e agrupar só olhariam para a página à vista.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport default function DataTable<T>({\r\n columns,\r\n data,\r\n allData,\r\n onDataNeeded,\r\n isAllDataLoading,\r\n alwaysClient,\r\n onClientModeChange,\r\n rowKey,\r\n onRowClick,\r\n onRowDoubleClick,\r\n rowClassName,\r\n isLoading,\r\n isError,\r\n maxHeight = '70vh',\r\n pageSize,\r\n pageSizeOptions,\r\n selectable,\r\n onSelectionChange,\r\n clearSelectionTrigger,\r\n storageKey,\r\n defaultMaxColumnWidth = 360,\r\n resizableColumns = true,\r\n sortFromFilter = true,\r\n filterMode = 'excel',\r\n defaultSortKey,\r\n defaultSortDir,\r\n viewRef,\r\n exportFileName,\r\n onExport,\r\n extraToolbar,\r\n emptySubLabel,\r\n emptyIcon,\r\n labels,\r\n totalRow = false,\r\n defaultGroupBy,\r\n}: DataTableProps<T>) {\r\n const L = { ...DEFAULT_LABELS, ...labels }\r\n\r\n // Larguras definidas pela pessoa (arrastar o limite do cabeçalho). Guardadas por tabela no localStorage.\r\n const [larguras, setLarguras] = useState<Record<string, number>>(() => {\r\n if (!storageKey) return {}\r\n try {\r\n return JSON.parse(localStorage.getItem(`dt_widths_${storageKey}`) ?? '{}') as Record<string, number>\r\n } catch {\r\n return {}\r\n }\r\n })\r\n useEffect(() => {\r\n if (!storageKey) return\r\n try {\r\n localStorage.setItem(`dt_widths_${storageKey}`, JSON.stringify(larguras))\r\n } catch {\r\n /* storage indisponível */\r\n }\r\n }, [larguras, storageKey])\r\n const larguraMax = (c: DataTableColumn<T>) => larguras[c.key] ?? c.maxWidth ?? (defaultMaxColumnWidth > 0 ? defaultMaxColumnWidth : undefined)\r\n const iniciarResize = (e: React.MouseEvent, key: string) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n const th = (e.currentTarget as HTMLElement).parentElement\r\n const x0 = e.clientX\r\n const w0 = th?.getBoundingClientRect().width ?? 120\r\n const mover = (ev: MouseEvent) => setLarguras((l) => ({ ...l, [key]: Math.max(60, Math.round(w0 + ev.clientX - x0)) }))\r\n const parar = () => {\r\n window.removeEventListener('mousemove', mover)\r\n window.removeEventListener('mouseup', parar)\r\n document.body.style.cursor = ''\r\n }\r\n document.body.style.cursor = 'col-resize'\r\n window.addEventListener('mousemove', mover)\r\n window.addEventListener('mouseup', parar)\r\n }\r\n const reporLargura = (key: string) =>\r\n setLarguras((l) => {\r\n const n = { ...l }\r\n delete n[key]\r\n return n\r\n })\r\n // Com `storageKey`, a ordenação e o agrupamento ficam na sessão como os filtros: ao voltar à tabela (ex.: mudar\r\n // de separador e regressar) a vista é a mesma. Valor guardado (mesmo vazio) ganha aos de omissão.\r\n const lerSessao = <V,>(sufixo: string): V | undefined => {\r\n if (!storageKey) return undefined\r\n try {\r\n const raw = sessionStorage.getItem(`dt_${sufixo}_${storageKey}`)\r\n return raw === null ? undefined : (JSON.parse(raw) as V)\r\n } catch {\r\n return undefined\r\n }\r\n }\r\n const [sort, setSort] = useState<{ key: string; dir: 1 | -1 } | null>(() => {\r\n const guardada = lerSessao<{ key: string; dir: 1 | -1 } | null>('sort')\r\n if (guardada !== undefined) return guardada && columns.some((c) => c.key === guardada.key) ? guardada : null\r\n return defaultSortKey ? { key: defaultSortKey, dir: defaultSortDir === 'desc' ? -1 : 1 } : null\r\n })\r\n const [columnFilters, setColumnFilters] = useState<Record<string, Set<string>>>(() => {\r\n if (!storageKey) return {}\r\n try {\r\n const raw = sessionStorage.getItem(`dt_filters_${storageKey}`)\r\n if (!raw) return {}\r\n const parsed = JSON.parse(raw) as Record<string, string[]>\r\n return Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, new Set(v)]))\r\n } catch {\r\n return {}\r\n }\r\n })\r\n // Filtro por intervalo, em colunas numéricas e de datas (`sortType`). Numa coluna como\r\n // \"Dias Penalização\" a lista de valores distintos é inútil — ninguém procura \"88 valores\" um a\r\n // um, quer é \"acima de 30\". Convive com a seleção por valor: se as duas estiverem ativas, a\r\n // linha tem de passar nas duas.\r\n const [rangeFilters, setRangeFilters] = useState<Record<string, { min?: number; max?: number }>>(() => {\r\n if (!storageKey) return {}\r\n try {\r\n const raw = sessionStorage.getItem(`dt_ranges_${storageKey}`)\r\n return raw ? (JSON.parse(raw) as Record<string, { min?: number; max?: number }>) : {}\r\n } catch {\r\n return {}\r\n }\r\n })\r\n const [groupByKeys, setGroupByKeys] = useState<string[]>(() =>\r\n (lerSessao<string[]>('group') ?? defaultGroupBy ?? []).filter((k) => columns.some((c) => c.key === k))\r\n )\r\n const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(new Set())\r\n const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set())\r\n const [openFilter, setOpenFilter] = useState<string | null>(null)\r\n const [filterSearch, setFilterSearch] = useState('')\r\n const filterRefs = useRef<Map<string, HTMLDivElement>>(new Map())\r\n const popupRef = useRef<HTMLDivElement | null>(null)\r\n // O popup é desenhado num portal, não dentro do `<th>`: a tabela vive num contentor com\r\n // `overflow: auto` e, quando tem poucas linhas, esse contentor é baixo e cortava o popup ao\r\n // fim de dois ou três valores. Fora da árvore da tabela, com `position: fixed`, deixa de haver\r\n // nada que o corte.\r\n const [popupPos, setPopupPos] = useState<{ top: number; left: number } | null>(null)\r\n\r\n const isGrouped = groupByKeys.length > 0\r\n\r\n useEffect(() => {\r\n if (!storageKey) return\r\n const serializavel = Object.fromEntries(Object.entries(columnFilters).map(([k, v]) => [k, Array.from(v)]))\r\n try {\r\n sessionStorage.setItem(`dt_filters_${storageKey}`, JSON.stringify(serializavel))\r\n sessionStorage.setItem(`dt_ranges_${storageKey}`, JSON.stringify(rangeFilters))\r\n sessionStorage.setItem(`dt_sort_${storageKey}`, JSON.stringify(sort))\r\n sessionStorage.setItem(`dt_group_${storageKey}`, JSON.stringify(groupByKeys))\r\n } catch {\r\n /* sessionStorage indisponível (private mode) — a tabela funciona na mesma, só não recorda. */\r\n }\r\n }, [columnFilters, rangeFilters, sort, groupByKeys, storageKey])\r\n\r\n useLayoutEffect(() => {\r\n if (!openFilter) {\r\n setPopupPos(null)\r\n return\r\n }\r\n const recalcular = () => {\r\n const el = filterRefs.current.get(openFilter)\r\n if (!el) return\r\n const r = el.getBoundingClientRect()\r\n // Tem de acompanhar o `w-72` do popup — é o que o impede de ficar fora do ecrã à direita.\r\n const largura = Math.min(288, window.innerWidth * 0.8)\r\n // Encosta à direita quando a coluna está no fim da tabela e o popup passaria do ecrã.\r\n const left = Math.max(8, Math.min(r.left, window.innerWidth - largura - 8))\r\n setPopupPos({ top: r.bottom + 6, left })\r\n }\r\n recalcular()\r\n // `true` na captura: apanha também o scroll do contentor da tabela, que não borbulha.\r\n window.addEventListener('scroll', recalcular, true)\r\n window.addEventListener('resize', recalcular)\r\n return () => {\r\n window.removeEventListener('scroll', recalcular, true)\r\n window.removeEventListener('resize', recalcular)\r\n }\r\n }, [openFilter])\r\n\r\n // Fecha o dropdown de filtro em clique fora (o popup está fora do `<th>`, daí os dois testes).\r\n useEffect(() => {\r\n const onDown = (e: MouseEvent) => {\r\n if (!openFilter) return\r\n const alvo = e.target as Node\r\n const botao = filterRefs.current.get(openFilter)\r\n if (botao?.contains(alvo) || popupRef.current?.contains(alvo)) return\r\n setOpenFilter(null)\r\n setFilterSearch('')\r\n }\r\n document.addEventListener('mousedown', onDown)\r\n return () => document.removeEventListener('mousedown', onDown)\r\n }, [openFilter])\r\n\r\n /**\r\n * Função de valor por chave de filtro. A chave tanto pode ser uma coluna como um\r\n * `extraFilters` pendurado no cabeçalho de outra coluna.\r\n */\r\n const resolverValor = (key: string): ((row: T) => string | number | null | undefined) | undefined => {\r\n const col = columns.find((c) => c.key === key)\r\n if (col) return (row: T) => rawValue(row, col)\r\n for (const c of columns) {\r\n const ef = c.extraFilters?.find((f) => f.key === key)\r\n if (ef) return ef.value\r\n }\r\n return undefined\r\n }\r\n\r\n const rotuloDe = (fn: (row: T) => string | number | null | undefined, row: T) => {\r\n const v = fn(row)\r\n return v == null || v === '' ? EMPTY : String(v)\r\n }\r\n\r\n const modoExcel = filterMode === 'excel'\r\n\r\n /**\r\n * No modo Excel é a **presença da chave** que liga o filtro, e não o tamanho do conjunto: sem\r\n * chave = sem filtro = tudo marcado no popover (o estado em que o Excel abre). Com chave passam\r\n * só os valores do conjunto, e um conjunto vazio é \"nada seleccionado\" — a tabela fica sem\r\n * linhas, como no Excel quando se desmarca o `(Selecionar tudo)`.\r\n */\r\n const activeFilters = useMemo(\r\n () => Object.entries(columnFilters).filter(([, s]) => s && (modoExcel || s.size > 0)),\r\n [columnFilters, modoExcel]\r\n )\r\n const activeRanges = useMemo(\r\n () => Object.entries(rangeFilters).filter(([, r]) => r && (r.min != null || r.max != null)),\r\n [rangeFilters]\r\n )\r\n const hasActiveFilters = activeFilters.length > 0 || activeRanges.length > 0\r\n\r\n /**\r\n * Com o dataset completo em mão, é sobre ele que se filtra, ordena e agrupa — não sobre\r\n * `data`. Sem isto, escolher no funil um valor que só existe noutra página dava uma tabela\r\n * vazia: as opções vinham de todos os dados mas o filtro corria só sobre a página.\r\n */\r\n const isClientMode = !!allData && (!!alwaysClient || hasActiveFilters || !!sort || isGrouped)\r\n const effectiveData = isClientMode ? allData! : data\r\n\r\n // Pedir o dataset completo. Não só no arranque: os filtros podem vir do sessionStorage ou\r\n // de uma vista reposta, e quem alimenta a tabela pode largar o dataset a meio.\r\n useEffect(() => {\r\n if (!allData && (hasActiveFilters || sort || isGrouped)) onDataNeeded?.()\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [allData, hasActiveFilters, sort, isGrouped])\r\n\r\n useEffect(() => {\r\n onClientModeChange?.(isClientMode)\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [isClientMode])\r\n\r\n useEffect(() => {\r\n if (clearSelectionTrigger === undefined) return\r\n setSelectedKeys(new Set())\r\n }, [clearSelectionTrigger])\r\n\r\n /**\r\n * Filtrar pelo cabeçalho também limpa a seleção.\r\n *\r\n * O que estava marcado era-o daquela lista: filtrar para outra e manter as marcas deixava\r\n * escolhidas linhas que já não se veem — e quem age sobre a seleção (uma ação em massa)\r\n * agia sobre elas na mesma. Avisa-se o ecrã, e não só se limpa por dentro: as caixas\r\n * ficavam vazias e o botão de cima continuava a dizer \"Tratar 250\" — e tratava-as.\r\n *\r\n * Ordenar não conta: são as mesmas linhas por outra ordem.\r\n */\r\n const chaveFiltrosCol = JSON.stringify(\r\n Object.entries(columnFilters)\r\n .map(([k, v]) => [k, [...v].sort()])\r\n .sort()\r\n )\r\n useEffect(() => {\r\n if (!selectable) return\r\n setSelectedKeys(new Set())\r\n onSelectionChange?.(new Set())\r\n // `onSelectionChange` fora das dependências de propósito: é uma função do ecrã, recriada a\r\n // cada render, e segui-la limpava a seleção sozinha.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [chaveFiltrosCol])\r\n\r\n /**\r\n * No modo Excel o distinto é o puro da coluna (ver `filterMode`), por isso não depende dos\r\n * filtros das outras. Fixar aqui a identidade evita recalcular a lista inteira a cada clique\r\n * numa caixa — que é precisamente o que uma coluna quase-única por linha não perdoa.\r\n */\r\n const cruzFiltros = modoExcel ? SEM_CRUZAMENTO : activeFilters\r\n const cruzRanges = modoExcel ? SEM_CRUZAMENTO : activeRanges\r\n\r\n /**\r\n * Distinct de uma chave, a partir do dataset completo quando ele existe: as opções do funil\r\n * são as da tabela toda, não as da página que calhou estar à vista.\r\n *\r\n * Os filtros das OUTRAS colunas aplicam-se (mas não o da própria): assim escolher \"Porto\"\r\n * numa coluna faz a coluna ao lado deixar de oferecer valores que já não existem — sem\r\n * impedir de alargar a escolha na coluna que se está a mexer.\r\n */\r\n const distinctValues = (key: string): string[] => {\r\n const valueFn = resolverValor(key)\r\n if (!valueFn) return []\r\n let fonte = allData ?? data\r\n for (const [outraKey, escolhidos] of cruzFiltros) {\r\n if (outraKey === key) continue\r\n const outraFn = resolverValor(outraKey)\r\n if (!outraFn) continue\r\n fonte = fonte.filter((row) => escolhidos.has(rotuloDe(outraFn, row)))\r\n }\r\n for (const [outraKey, r] of cruzRanges) {\r\n if (outraKey === key) continue\r\n const col = columns.find((c) => c.key === outraKey)\r\n if (!col) continue\r\n fonte = fonte.filter((row) => {\r\n const n = valorParaIntervalo(row, col)\r\n if (!Number.isFinite(n)) return false\r\n if (r.min != null && n < r.min) return false\r\n if (r.max != null && n > r.max) return false\r\n return true\r\n })\r\n }\r\n const set = new Set<string>()\r\n fonte.forEach((row) => set.add(rotuloDe(valueFn, row)))\r\n return Array.from(set).sort(ordenarRotulos)\r\n }\r\n\r\n // Memoizado ao abrir/mudar de coluna (não a cada tecla da pesquisa) — numa coluna quase-única\r\n // por linha, recalcular o distinct (iterar + Set + sort) em cada keystroke era o grosso da\r\n // lentidão do popup, não só a 1ª abertura.\r\n const openFilterOptions = useMemo(() => {\r\n if (!openFilter) return []\r\n return distinctValues(openFilter)\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [openFilter, data, allData, columns, cruzFiltros, cruzRanges])\r\n\r\n /** Versão em minúsculas do distinct, calculada uma vez por abertura — a pesquisa do popup\r\n * corria `v.toLowerCase()` sobre a lista inteira a cada tecla. */\r\n const openFilterOptionsLower = useMemo(() => openFilterOptions.map((v) => v.toLowerCase()), [openFilterOptions])\r\n\r\n /** Opções a desenhar (no máximo `MAX_OPCOES_FILTRO`) + total de correspondências, para o\r\n * rodapé \"a mostrar X de Y\". Um só varrimento, sem alocar o array completo dos matches. */\r\n const opcoesVisiveis = useMemo(() => {\r\n const q = filterSearch.toLowerCase()\r\n // No modo Excel a pesquisa aceita curingas; no simples mantém-se o \"contém\" de sempre.\r\n const corresponde = q ? (modoExcel ? pesquisaCuringa(q) : (v: string) => v.includes(q)) : null\r\n const opts: string[] = []\r\n // `correspondentes` é tudo o que a pesquisa apanha, não só o que se desenha: com 200\r\n // correspondências e 50 desenhadas, o `(Selecionar tudo)` tem de apanhar as 200 — é o que o\r\n // Excel faz, e o contrário seria uma armadilha silenciosa.\r\n const correspondentes: string[] = []\r\n for (let i = 0; i < openFilterOptions.length; i++) {\r\n if (corresponde && !corresponde(openFilterOptionsLower[i])) continue\r\n correspondentes.push(openFilterOptions[i])\r\n if (opts.length < MAX_OPCOES_FILTRO) opts.push(openFilterOptions[i])\r\n }\r\n return { opts, total: correspondentes.length, correspondentes }\r\n }, [openFilterOptions, openFilterOptionsLower, filterSearch, modoExcel])\r\n\r\n // Filtrar e ordenar são funções sobre uma lista qualquer (não só sobre a vista atual): é o\r\n // que permite reaplicar a mesma vista ao dataset completo — ver `viewRef`/`DataTableView`.\r\n const filtrar = (rows: T[]) => {\r\n if (activeFilters.length === 0 && activeRanges.length === 0) return rows\r\n return rows.filter(\r\n (row) =>\r\n activeFilters.every(([key, sel]) => {\r\n const fn = resolverValor(key)\r\n return fn ? sel.has(rotuloDe(fn, row)) : true\r\n }) &&\r\n activeRanges.every(([key, r]) => {\r\n const col = columns.find((c) => c.key === key)\r\n if (!col) return true\r\n const n = valorParaIntervalo(row, col)\r\n // Sem valor não passa num intervalo — \"maior que 30\" não inclui \"sem dias\", e um\r\n // intervalo de datas não inclui linhas sem data.\r\n if (!Number.isFinite(n)) return false\r\n if (r.min != null && n < r.min) return false\r\n if (r.max != null && n > r.max) return false\r\n return true\r\n })\r\n )\r\n }\r\n\r\n const ordenar = (rows: T[]) => {\r\n if (!sort) return rows\r\n const col = columns.find((c) => c.key === sort.key)\r\n if (!col) return rows\r\n return [...rows].sort((a, b) => compare(rawValue(a, col), rawValue(b, col), col.sortType) * sort.dir)\r\n }\r\n\r\n const filtered = useMemo(\r\n () => filtrar(effectiveData),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [effectiveData, activeFilters, activeRanges, columns]\r\n )\r\n\r\n const sorted = useMemo(\r\n () => ordenar(filtered),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [filtered, sort, columns]\r\n )\r\n\r\n // ─── Agrupamento ───────────────────────────────────────────────────────────\r\n\r\n const alternarGrupo = (key: string) => {\r\n if (!allData) onDataNeeded?.()\r\n setGroupByKeys((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]))\r\n setCollapsedGroups(new Set())\r\n }\r\n const removerGrupo = (key: string) => {\r\n setGroupByKeys((prev) => prev.filter((k) => k !== key))\r\n setCollapsedGroups(new Set())\r\n }\r\n const limparGrupos = () => {\r\n setGroupByKeys([])\r\n setCollapsedGroups(new Set())\r\n }\r\n const alternarColapso = (path: string) =>\r\n setCollapsedGroups((prev) => {\r\n const n = new Set(prev)\r\n if (n.has(path)) n.delete(path)\r\n else n.add(path)\r\n return n\r\n })\r\n\r\n const construirGrupos = (items: T[], keys: string[], nivel: number, pathPai: string): GrupoNo<T>[] => {\r\n if (nivel >= keys.length) return []\r\n const key = keys[nivel]\r\n const col = columns.find((c) => c.key === key)\r\n if (!col) return []\r\n // `groupSortKey` manda na ORDEM dos grupos; o rótulo continua a ser o valor da coluna.\r\n const ordemFn = col.groupSortKey\r\n const mapa = new Map<string, { ordem: string; items: T[] }>()\r\n items.forEach((item) => {\r\n const rotulo = filterKey(item, col)\r\n const ordem = ordemFn ? String(ordemFn(item) ?? '') : rotulo\r\n const atual = mapa.get(rotulo)\r\n if (atual) atual.items.push(item)\r\n else mapa.set(rotulo, { ordem, items: [item] })\r\n })\r\n return Array.from(mapa.entries())\r\n .sort(([ra, a], [rb, b]) => (ordemFn ? COLLATOR.compare(a.ordem, b.ordem) : ordenarRotulos(ra, rb)))\r\n .map(([val, { ordem, items: itensGrupo }]) => {\r\n const path = pathPai ? `${pathPai}\\u0000${val}` : val\r\n return { val, ordem, path, items: itensGrupo, filhos: construirGrupos(itensGrupo, keys, nivel + 1, path) }\r\n })\r\n }\r\n\r\n const grupos = useMemo(\r\n () => (isGrouped ? construirGrupos(sorted, groupByKeys, 0, '') : []),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [sorted, groupByKeys, isGrouped, columns]\r\n )\r\n\r\n // ─── Paginação (desligada ao agrupar) ──────────────────────────────────────\r\n\r\n const [page, setPage] = useState(1)\r\n const [pageSizeAtual, setPageSizeAtual] = useState<number | undefined>(pageSize)\r\n useEffect(() => setPageSizeAtual(pageSize), [pageSize])\r\n useEffect(() => {\r\n setPage(1)\r\n }, [columnFilters, rangeFilters, sort, groupByKeys, pageSizeAtual])\r\n const paginando = !!pageSizeAtual && !isGrouped\r\n const totalPages = paginando ? Math.max(1, Math.ceil(sorted.length / pageSizeAtual!)) : 1\r\n const currentPage = Math.min(page, totalPages)\r\n const pageData = paginando ? sorted.slice((currentPage - 1) * pageSizeAtual!, currentPage * pageSizeAtual!) : sorted\r\n\r\n // ─── Seleção ───────────────────────────────────────────────────────────────\r\n\r\n const chaveDe = (row: T, i: number) => String(rowKey ? rowKey(row, i) : i)\r\n\r\n /**\r\n * Índice de cada linha na lista completa. Ao agrupar, as linhas são desenhadas grupo a\r\n * grupo e o índice local recomeçava em 0 em cada um — o que dava chaves React repetidas,\r\n * a zebra a reiniciar e, sem `rowKey`, linhas de grupos diferentes a partilhar a mesma\r\n * chave de seleção.\r\n */\r\n const indicePorLinha = useMemo(() => {\r\n const m = new Map<T, number>()\r\n sorted.forEach((r, i) => m.set(r, i))\r\n return m\r\n }, [sorted])\r\n const visibleKeys = useMemo(\r\n () => pageData.map((row, i) => chaveDe(row, i)),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [pageData, rowKey]\r\n )\r\n const alternarLinha = (key: string) =>\r\n setSelectedKeys((prev) => {\r\n const next = new Set(prev)\r\n if (next.has(key)) next.delete(key)\r\n else next.add(key)\r\n onSelectionChange?.(next)\r\n return next\r\n })\r\n const alternarTodas = () =>\r\n setSelectedKeys((prev) => {\r\n const todasMarcadas = visibleKeys.length > 0 && visibleKeys.every((k) => prev.has(k))\r\n const next = new Set(prev)\r\n if (todasMarcadas) visibleKeys.forEach((k) => next.delete(k))\r\n else visibleKeys.forEach((k) => next.add(k))\r\n onSelectionChange?.(next)\r\n return next\r\n })\r\n const todasVisiveisMarcadas = visibleKeys.length > 0 && visibleKeys.every((k) => selectedKeys.has(k))\r\n const algumaVisivelMarcada = visibleKeys.some((k) => selectedKeys.has(k)) && !todasVisiveisMarcadas\r\n\r\n // ─── Handle externo da vista ───────────────────────────────────────────────\r\n\r\n // Sem deps: é lido de fora a pedido (ex: ao exportar), tem de estar sempre coerente com o\r\n // último render.\r\n useEffect(() => {\r\n if (!viewRef) return\r\n viewRef.current = {\r\n apply: (rows) => ordenar(filtrar(rows)),\r\n rows: sorted,\r\n hasFilters: hasActiveFilters,\r\n precisaDataset: hasActiveFilters || !!sort || isGrouped,\r\n estado: () => ({\r\n columnFilters: Object.fromEntries(activeFilters.map(([k, v]) => [k, [...v]])),\r\n rangeFilters: Object.fromEntries(activeRanges),\r\n sortKey: sort?.key ?? null,\r\n sortDir: sort ? (sort.dir === 1 ? 'asc' : 'desc') : 'asc',\r\n groupBy: groupByKeys,\r\n }),\r\n repor: (e) => {\r\n setColumnFilters(Object.fromEntries(Object.entries(e?.columnFilters ?? {}).map(([k, v]) => [k, new Set(v)])))\r\n setRangeFilters(e?.rangeFilters ?? {})\r\n setSort(e?.sortKey ? { key: e.sortKey, dir: e.sortDir === 'desc' ? -1 : 1 } : null)\r\n setGroupByKeys(e?.groupBy ?? [])\r\n // Grupos recolhidos não se guardam: a vista diz por que se agrupa, não o que estava\r\n // aberto no momento em que foi gravada.\r\n setCollapsedGroups(new Set())\r\n const precisaTudo =\r\n !!e?.sortKey ||\r\n !!e?.groupBy?.length ||\r\n Object.keys(e?.columnFilters ?? {}).length > 0 ||\r\n Object.keys(e?.rangeFilters ?? {}).length > 0\r\n if (precisaTudo && !allData) onDataNeeded?.()\r\n },\r\n }\r\n })\r\n\r\n // ─── Ações de filtro/ordenação ─────────────────────────────────────────────\r\n\r\n const toggleSort = (key: string) => {\r\n if (!allData) onDataNeeded?.()\r\n setSort((s) => (s?.key === key ? (s.dir === 1 ? { key, dir: -1 } : null) : { key, dir: 1 }))\r\n }\r\n\r\n /**\r\n * Selecção actual da coluna aberta, já materializada.\r\n *\r\n * No modo Excel, sem entrada no mapa está tudo seleccionado — e é aqui que isso passa de\r\n * implícito a explícito, na primeira vez que se desmarca alguma coisa. Só serve a coluna\r\n * aberta: `openFilterOptions` é dela.\r\n */\r\n const selecaoDaColuna = (key: string) =>\r\n new Set(columnFilters[key] ?? (modoExcel ? openFilterOptions : []))\r\n\r\n const aplicarSelecao = (key: string, sel: Set<string>) =>\r\n setColumnFilters((prev) => {\r\n const proximo = { ...prev }\r\n // Tudo marcado é o mesmo que não haver filtro. Apagar a chave desliga o funil e tira o\r\n // chip, em vez de deixar um filtro que não filtra nada.\r\n if (modoExcel && sel.size === openFilterOptions.length) delete proximo[key]\r\n else proximo[key] = sel\r\n return proximo\r\n })\r\n\r\n const toggleFilterValue = (key: string, val: string) => {\r\n if (!allData) onDataNeeded?.()\r\n const sel = selecaoDaColuna(key)\r\n if (sel.has(val)) sel.delete(val)\r\n else sel.add(val)\r\n aplicarSelecao(key, sel)\r\n }\r\n\r\n /** `(Selecionar tudo)` — trabalha sobre os `correspondentes`, não sobre o que está desenhado. */\r\n const alternarTodos = (key: string, marcar: boolean) => {\r\n if (!allData) onDataNeeded?.()\r\n const sel = selecaoDaColuna(key)\r\n for (const v of opcoesVisiveis.correspondentes) {\r\n if (marcar) sel.add(v)\r\n else sel.delete(v)\r\n }\r\n aplicarSelecao(key, sel)\r\n }\r\n\r\n const setRange = (key: string, campo: 'min' | 'max', valor: string, tipo: DataTableColumn<T>['sortType']) => {\r\n if (!allData) onDataNeeded?.()\r\n setRangeFilters((prev) => {\r\n const atual = { ...(prev[key] ?? {}) }\r\n // Datas chegam do `<input type=\"date\">` em 'aaaa-mm-dd'; guarda-se o timestamp para o\r\n // filtro comparar sempre números. O `max` de uma data cobre o dia todo.\r\n const n = tipo === 'date' ? timestampDeData(valor) + (campo === 'max' ? 86_399_999 : 0) : Number(valor)\r\n if (valor === '' || !Number.isFinite(n)) delete atual[campo]\r\n else atual[campo] = n\r\n const next = { ...prev }\r\n if (atual.min == null && atual.max == null) delete next[key]\r\n else next[key] = atual\r\n return next\r\n })\r\n }\r\n\r\n const clearColumnFilter = (key: string) => {\r\n setColumnFilters((prev) => {\r\n const n = { ...prev }\r\n delete n[key]\r\n return n\r\n })\r\n setRangeFilters((prev) => {\r\n const n = { ...prev }\r\n delete n[key]\r\n return n\r\n })\r\n }\r\n\r\n const clearAllFilters = () => {\r\n setColumnFilters({})\r\n setRangeFilters({})\r\n }\r\n\r\n const temIntervalo = (key: string) => {\r\n const r = rangeFilters[key]\r\n return !!r && (r.min != null || r.max != null)\r\n }\r\n // No modo Excel o funil acende com a chave presente, mesmo com o conjunto vazio.\r\n const temFiltro = (key: string) =>\r\n (modoExcel ? columnFilters[key] !== undefined : (columnFilters[key]?.size ?? 0) > 0) || temIntervalo(key)\r\n\r\n const handleExport = () => {\r\n if (onExport) return onExport(sorted)\r\n const csv = toCsv(columns, sorted)\r\n const blob = new Blob([`${csv}`], { type: 'text/csv;charset=utf-8;' })\r\n const url = URL.createObjectURL(blob)\r\n const a = document.createElement('a')\r\n a.href = url\r\n a.download = `${exportFileName}.csv`\r\n a.click()\r\n URL.revokeObjectURL(url)\r\n }\r\n\r\n const alignClass = (a?: DataTableColumn<T>['align']) =>\r\n a === 'right' ? 'text-right' : a === 'center' ? 'text-center' : 'text-left'\r\n\r\n /** `sticky: true` mantem o significado antigo: presa a esquerda. */\r\n const ePresaAEsquerda = (c: DataTableColumn<T>) => c.sticky === true || c.sticky === 'left'\r\n\r\n // Offset acumulado por coluna sticky — sem isto, 2+ colunas sticky ficavam todas em\r\n // `left:0`, sobrepostas (a 2.ª por cima da 1.ª). Colunas sticky sem `width` usam um valor\r\n // por defeito razoável para a acumulação (não afeta a largura real da coluna).\r\n const stickyLeft = useMemo(() => {\r\n const offsets: Record<string, number> = {}\r\n let acc = selectable ? 40 : 0\r\n for (const c of columns) {\r\n if (!ePresaAEsquerda(c)) continue\r\n offsets[c.key] = acc\r\n acc += c.width ?? DEFAULT_STICKY_WIDTH\r\n }\r\n return offsets\r\n }, [columns, selectable])\r\n\r\n // À direita acumula-se ao contrário: a última coluna encosta a 0 e as\r\n // anteriores empilham-se por cima da largura das seguintes.\r\n const stickyRight = useMemo(() => {\r\n const offsets: Record<string, number> = {}\r\n let acc = 0\r\n for (let i = columns.length - 1; i >= 0; i--) {\r\n const c = columns[i]\r\n if (c.sticky !== 'right') continue\r\n offsets[c.key] = acc\r\n acc += c.width ?? DEFAULT_STICKY_WIDTH\r\n }\r\n return offsets\r\n }, [columns])\r\n\r\n const estiloPreso = (c: DataTableColumn<T>): React.CSSProperties | undefined => {\r\n if (ePresaAEsquerda(c)) return { left: stickyLeft[c.key] }\r\n if (c.sticky === 'right') return { right: stickyRight[c.key] }\r\n return undefined\r\n }\r\n\r\n const nColunas = columns.length + (selectable ? 1 : 0)\r\n\r\n // Enquanto o dataset completo não chega, filtrar/ordenar/agrupar só olharia para a página —\r\n // melhor esperar do que mostrar uma lista errada por baixo de um total nos milhares.\r\n const aEsperarDataset = !allData && !!isAllDataLoading && (hasActiveFilters || !!sort || isGrouped)\r\n\r\n // Antes dos returns de 'a carregar'/erro: um hook depois deles muda a ordem dos hooks entre renders.\r\n // Por omissão o cabeçalho oferece ordenar, filtrar e agrupar (ver `filterable`/`groupable`). Colunas sem dados\r\n // em nenhuma linha (ex.: botões de ações) não têm o que filtrar nem agrupar.\r\n const linhasBase = allData ?? data\r\n const semDados = useMemo(\r\n () => new Set(columns.filter((c) => !linhasBase.some((r) => rawValue(r, c) !== null)).map((c) => c.key)),\r\n [columns, linhasBase]\r\n )\r\n const filtravel = (c: DataTableColumn<T>) => c.filterable ?? !semDados.has(c.key)\r\n const agrupavel = (c: DataTableColumn<T>) =>\r\n c.groupable ?? (!semDados.has(c.key) && !c.groupAggregate && (!c.sortType || c.sortType === 'text'))\r\n\r\n if (isLoading) {\r\n return <div className=\"py-16 text-center text-sm font-bold uppercase tracking-widest text-slate-400\">{L.loading}</div>\r\n }\r\n if (isError) {\r\n return <div className=\"py-16 text-center text-sm font-bold uppercase tracking-widest text-red-400\">{L.error}</div>\r\n }\r\n\r\n // ─── Sub-render: popup de filtro ───────────────────────────────────────────\r\n\r\n const renderPopup = (key: string, col: DataTableColumn<T> | undefined, tituloExtra?: string) => {\r\n const { opts, total: totalOpcoes } = opcoesVisiveis\r\n const sel = columnFilters[key]\r\n const isFiltered = temFiltro(key)\r\n const podeOrdenar = !!sortFromFilter && !!col && col.sortable !== false\r\n const temRange = !!col && (col.sortType === 'number' || col.sortType === 'date')\r\n return createPortal(\r\n <div\r\n ref={popupRef}\r\n style={{ position: 'fixed', top: popupPos!.top, left: popupPos!.left }}\r\n className=\"z-[100] w-72 max-w-[80vw] bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 shadow-2xl rounded-xl overflow-hidden\"\r\n >\r\n {tituloExtra && (\r\n <div className=\"px-3 pt-2.5 pb-2 border-b border-slate-100 dark:border-slate-800\">\r\n <span className=\"text-[9px] font-black uppercase tracking-widest text-proef-blue dark:text-proef-turquoise\">{tituloExtra}</span>\r\n </div>\r\n )}\r\n {!allData && isAllDataLoading && (\r\n // As opções à vista ainda são só as de `data`. Dizê-lo é melhor do que deixar\r\n // escolher de uma lista incompleta.\r\n <div className=\"px-2 py-1.5 bg-amber-50 dark:bg-amber-500/10 border-b border-amber-100 dark:border-amber-500/20 text-[10px] text-amber-700 dark:text-amber-300\">\r\n {L.loadingOptions}\r\n </div>\r\n )}\r\n {podeOrdenar && (\r\n <div className=\"border-b border-slate-100 dark:border-slate-800 p-1.5 space-y-0.5\">\r\n <button\r\n onClick={() => {\r\n if (!allData) onDataNeeded?.()\r\n setSort({ key, dir: 1 })\r\n }}\r\n className={`appearance-none bg-transparent border-0 cursor-pointer w-full flex items-center gap-2 px-2.5 py-1.5 text-[11px] font-semibold text-left hover:bg-slate-50 dark:hover:bg-slate-800 ${\r\n sort?.key === key && sort.dir === 1 ? 'text-proef-blue dark:text-proef-turquoise bg-slate-50 dark:bg-slate-800' : 'text-slate-600 dark:text-slate-300'\r\n }`}\r\n >\r\n ▲ {L.sortAsc}\r\n </button>\r\n <button\r\n onClick={() => {\r\n if (!allData) onDataNeeded?.()\r\n setSort({ key, dir: -1 })\r\n }}\r\n className={`appearance-none bg-transparent border-0 cursor-pointer w-full flex items-center gap-2 px-2.5 py-1.5 text-[11px] font-semibold text-left hover:bg-slate-50 dark:hover:bg-slate-800 ${\r\n sort?.key === key && sort.dir === -1 ? 'text-proef-blue dark:text-proef-turquoise bg-slate-50 dark:bg-slate-800' : 'text-slate-600 dark:text-slate-300'\r\n }`}\r\n >\r\n ▼ {L.sortDesc}\r\n </button>\r\n </div>\r\n )}\r\n <div className=\"p-2\">\r\n {temRange && (\r\n // Intervalo antes da lista: numa coluna numérica ou de datas é o que se procura\r\n // (\"acima de 30 dias\", \"a partir de julho\"), não um valor exato numa lista de dezenas.\r\n <div className=\"flex items-center gap-1.5 mb-2\">\r\n <input\r\n type={col!.sortType === 'date' ? 'date' : 'number'}\r\n defaultValue=\"\"\r\n onChange={(e) => setRange(key, 'min', e.target.value, col!.sortType)}\r\n placeholder={L.min}\r\n title={L.min}\r\n className=\"w-1/2 bg-slate-50 dark:bg-slate-800 dark:text-slate-100 dark:[color-scheme:dark] border border-slate-200 dark:border-slate-700 rounded-lg px-2 py-1.5 text-xs outline-none focus:border-proef-blue dark:focus:border-proef-turquoise\"\r\n />\r\n <input\r\n type={col!.sortType === 'date' ? 'date' : 'number'}\r\n defaultValue=\"\"\r\n onChange={(e) => setRange(key, 'max', e.target.value, col!.sortType)}\r\n placeholder={L.max}\r\n title={L.max}\r\n className=\"w-1/2 bg-slate-50 dark:bg-slate-800 dark:text-slate-100 dark:[color-scheme:dark] border border-slate-200 dark:border-slate-700 rounded-lg px-2 py-1.5 text-xs outline-none focus:border-proef-blue dark:focus:border-proef-turquoise\"\r\n />\r\n </div>\r\n )}\r\n <input\r\n autoFocus\r\n value={filterSearch}\r\n onChange={(e) => setFilterSearch(e.target.value)}\r\n placeholder={L.search}\r\n className=\"w-full bg-slate-50 dark:bg-slate-800 dark:text-slate-100 border border-slate-200 dark:border-slate-700 rounded-lg px-2 py-1.5 text-xs outline-none focus:border-proef-blue dark:focus:border-proef-turquoise mb-2\"\r\n />\r\n {/* `overflow-x-hidden` explícito: com só `overflow-y-auto`, o CSS calcula\r\n `overflow-x: auto` e a barra vertical rouba largura ao conteúdo até aparecer uma\r\n segunda barra, horizontal, que não faz falta nenhuma. */}\r\n <div className=\"max-h-64 overflow-y-auto overflow-x-hidden space-y-0.5\">\r\n {modoExcel && opts.length > 0 && (() => {\r\n // Tri-state sobre os valores que a pesquisa apanha: ✓ todos, – alguns, vazio nenhum.\r\n const marcados = opcoesVisiveis.correspondentes.filter((v) => (sel ? sel.has(v) : true)).length\r\n const todos = marcados === opcoesVisiveis.correspondentes.length\r\n const alguns = marcados > 0 && !todos\r\n return (\r\n <button\r\n onClick={() => alternarTodos(key, !todos)}\r\n className=\"appearance-none bg-transparent border-0 border-b border-slate-100 dark:border-slate-800 cursor-pointer w-full flex items-center gap-2 px-1.5 py-1 mb-0.5 hover:bg-slate-50 dark:hover:bg-slate-800 text-left normal-case tracking-normal\"\r\n >\r\n <span\r\n className={`w-3.5 h-3.5 flex items-center justify-center border shrink-0 rounded ${\r\n todos || alguns ? 'bg-proef-navy dark:bg-proef-turquoise border-proef-navy dark:border-proef-turquoise text-white dark:text-proef-navy' : 'border-slate-300 dark:border-slate-600'\r\n }`}\r\n >\r\n {todos && <span className=\"text-[9px] leading-none\">✓</span>}\r\n {alguns && <span className=\"text-[9px] leading-none\">–</span>}\r\n </span>\r\n <span className=\"text-xs text-slate-700 dark:text-slate-200 font-semibold break-words\">\r\n {filterSearch ? L.selectAllResults : L.selectAll}\r\n </span>\r\n </button>\r\n )\r\n })()}\r\n {opts.length === 0 ? (\r\n <p className=\"text-[11px] text-slate-400 text-center py-3\">{L.noOptions}</p>\r\n ) : (\r\n opts.map((v) => {\r\n // Sem entrada no mapa está tudo marcado, daí o `sel ? … : modoExcel`.\r\n const checked = sel ? sel.has(v) : modoExcel\r\n return (\r\n <button\r\n key={v}\r\n onClick={() => toggleFilterValue(key, v)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer w-full flex items-center gap-2 px-1.5 py-1 hover:bg-slate-50 dark:hover:bg-slate-800 text-left normal-case tracking-normal\"\r\n >\r\n <span\r\n className={`w-3.5 h-3.5 flex items-center justify-center border shrink-0 rounded ${\r\n checked ? 'bg-proef-navy dark:bg-proef-turquoise border-proef-navy dark:border-proef-turquoise text-white dark:text-proef-navy' : 'border-slate-300 dark:border-slate-600'\r\n }`}\r\n >\r\n {checked && <span className=\"text-[9px] leading-none\">✓</span>}\r\n </span>\r\n <span className=\"text-xs text-slate-700 dark:text-slate-200 font-normal break-words\">{v}</span>\r\n </button>\r\n )\r\n })\r\n )}\r\n </div>\r\n {totalOpcoes > opts.length && (\r\n <p className=\"text-[10.5px] text-slate-400 text-center pt-2\">\r\n {L.moreOptions.replace('{shown}', String(opts.length)).replace('{total}', String(totalOpcoes))}\r\n </p>\r\n )}\r\n {isFiltered && (\r\n <button\r\n onClick={() => clearColumnFilter(key)}\r\n className=\"appearance-none bg-transparent border-0 border-t border-slate-100 dark:border-slate-800 cursor-pointer w-full mt-2 pt-2 text-[10px] font-black uppercase tracking-wider text-red-400 hover:text-red-500\"\r\n >\r\n {L.clear}\r\n </button>\r\n )}\r\n </div>\r\n </div>,\r\n document.body\r\n )\r\n }\r\n\r\n const botaoFunil = (key: string, col: DataTableColumn<T> | undefined, extra?: { label: string }) => {\r\n const isOpen = openFilter === key\r\n const isFiltered = temFiltro(key)\r\n return (\r\n <div\r\n key={key}\r\n className=\"relative\"\r\n ref={(el) => {\r\n if (el) filterRefs.current.set(key, el)\r\n else filterRefs.current.delete(key)\r\n }}\r\n >\r\n <button\r\n onClick={() => {\r\n // Abrir o funil é a 1.ª vez que as opções de todas as páginas fazem falta — é aqui\r\n // que se pede o dataset completo.\r\n if (!isOpen && !allData) onDataNeeded?.()\r\n setOpenFilter(isOpen ? null : key)\r\n setFilterSearch('')\r\n }}\r\n className={`appearance-none border-0 bg-transparent cursor-pointer p-0.5 transition-colors ${\r\n extra\r\n ? isFiltered || isOpen\r\n ? 'text-violet-600 dark:text-violet-300'\r\n : 'text-slate-300 dark:text-slate-600 hover:text-slate-500 dark:hover:text-slate-300'\r\n : isFiltered || isOpen\r\n ? 'text-proef-turquoise'\r\n : 'text-slate-300 dark:text-slate-600 hover:text-slate-500 dark:hover:text-slate-300'\r\n }`}\r\n // Sem isto o funil e um botao sem nome acessivel: nem leitor de ecra nem teste lhe\r\n // chegam pelo nome da coluna.\r\n title={extra?.label ?? col?.label}\r\n >\r\n <Funnel size={extra ? 10 : 11} className={isFiltered ? 'fill-current' : ''} />\r\n </button>\r\n {isOpen && popupPos && renderPopup(key, col, extra?.label)}\r\n </div>\r\n )\r\n }\r\n\r\n // ─── Sub-render: linhas ────────────────────────────────────────────────────\r\n\r\n const renderLinha = (row: T, i: number) => {\r\n const k = chaveDe(row, i)\r\n const marcada = selectable && selectedKeys.has(k)\r\n return (\r\n <tr\r\n key={k}\r\n onClick={() => onRowClick?.(row)}\r\n onDoubleClick={() => onRowDoubleClick?.(row)}\r\n className={`group even:bg-slate-50/50 dark:even:bg-slate-800/40 hover:bg-slate-100/70 dark:hover:bg-slate-800/70 transition-colors ${\r\n onRowClick || onRowDoubleClick ? 'cursor-pointer' : ''\r\n } ${marcada ? 'bg-proef-turquoise/5' : ''} ${rowClassName?.(row) ?? ''}`}\r\n >\r\n {selectable && (\r\n <td\r\n className=\"px-3 py-2.5 border-b border-slate-100 dark:border-slate-800 w-10\"\r\n onClick={(e) => {\r\n e.stopPropagation()\r\n alternarLinha(k)\r\n }}\r\n >\r\n <span\r\n className={`w-4 h-4 flex items-center justify-center border transition-colors rounded ${\r\n marcada ? 'bg-proef-navy dark:bg-proef-turquoise border-proef-navy dark:border-proef-turquoise text-white dark:text-proef-navy' : 'border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500'\r\n }`}\r\n >\r\n {marcada && <span className=\"text-[9px] leading-none\">✓</span>}\r\n </span>\r\n </td>\r\n )}\r\n {columns.map((c) => (\r\n <td\r\n key={c.key}\r\n style={estiloPreso(c)}\r\n // Fundo sólido explícito (não `bg-inherit`) — uma coluna sticky com fundo\r\n // transparente/translúcido deixa o conteúdo que passa por baixo, ao fazer\r\n // scroll horizontal, transparecer por trás do texto (sobreposição ilegível).\r\n // `group-hover` porque `:hover` isolado na célula não reage ao hover na linha.\r\n className={`px-3 py-2.5 border-b border-slate-100 dark:border-slate-800 whitespace-nowrap ${alignClass(c.align)} ${\r\n c.sticky ? `sticky z-[1] ${c.sticky === 'right' ? 'border-l border-slate-200 dark:border-slate-700' : ''} ${i % 2 === 1 ? 'bg-slate-50 dark:bg-slate-800' : 'bg-white dark:bg-slate-900'} group-hover:bg-slate-100 dark:group-hover:bg-slate-800` : ''\r\n } ${c.cellClassName ?? ''}`}\r\n >\r\n {(() => {\r\n const conteudo = c.render ? c.render(row) : rawValue(row, c) ?? <span className=\"text-slate-400\">{EMPTY}</span>\r\n const max = larguraMax(c)\r\n if (!max) return conteudo\r\n const v = rawValue(row, c)\r\n return (\r\n <div className=\"overflow-hidden text-ellipsis\" style={{ maxWidth: max, width: larguras[c.key] }} title={v !== null && v !== undefined ? String(v) : undefined}>\r\n {conteudo}\r\n </div>\r\n )\r\n })()}\r\n </td>\r\n ))}\r\n </tr>\r\n )\r\n }\r\n\r\n // Colunas com valor nas linhas de grupo/total: o rótulo ocupa as colunas até à primeira com valor; daí em\r\n // diante cada coluna tem a sua célula, alinhada com as linhas.\r\n\r\n const primeiraComValor = columns.findIndex((c) => !!c.groupAggregate)\r\n const colunasComValor = primeiraComValor >= 0 ? columns.slice(primeiraComValor) : []\r\n const colunasRotuloGrupo = primeiraComValor >= 0 ? primeiraComValor + (selectable ? 1 : 0) : nColunas\r\n\r\n const renderGrupos = (nos: GrupoNo<T>[], nivel: number): React.ReactNode =>\r\n nos.map((no) => {\r\n const recolhido = collapsedGroups.has(no.path)\r\n return (\r\n <Fragment key={no.path}>\r\n <tr className=\"bg-slate-50/80 dark:bg-slate-800/60 cursor-pointer select-none\" onClick={() => alternarColapso(no.path)}>\r\n <td colSpan={colunasRotuloGrupo} className=\"px-3 py-1.5 border-b border-t border-slate-100 dark:border-slate-800\">\r\n <div className=\"flex items-center gap-1.5\" style={{ paddingLeft: nivel * 16 }}>\r\n <Caret\r\n size={11}\r\n className={`text-proef-blue/60 dark:text-proef-turquoise/70 transition-transform shrink-0 ${recolhido ? '' : 'rotate-90'}`}\r\n />\r\n <span className=\"text-[10px] font-black uppercase tracking-[0.15em] text-proef-blue/70 dark:text-proef-turquoise/80\">{no.val}</span>\r\n <span className=\"text-[10px] text-slate-400 ml-1\">({no.items.length})</span>\r\n </div>\r\n </td>\r\n {colunasComValor.map((c) => (\r\n <td key={c.key} className={`px-3 py-1.5 border-b border-t border-slate-100 dark:border-slate-800 whitespace-nowrap text-xs font-bold text-slate-700 dark:text-slate-200 ${alignClass(c.align)}`}>\r\n {c.groupAggregate?.(no.items)}\r\n </td>\r\n ))}\r\n </tr>\r\n {!recolhido &&\r\n (no.filhos.length > 0\r\n ? renderGrupos(no.filhos, nivel + 1)\r\n : no.items.map((row) => renderLinha(row, indicePorLinha.get(row) ?? 0)))}\r\n </Fragment>\r\n )\r\n })\r\n\r\n // ─── Render ────────────────────────────────────────────────────────────────\r\n\r\n return (\r\n <div>\r\n <div className=\"flex items-center gap-2 mb-2.5\">\r\n <span className=\"text-xs text-slate-500 dark:text-slate-400\">{L.results.replace('{count}', String(sorted.length))}</span>\r\n <div className=\"ml-auto flex items-center gap-2\">\r\n {extraToolbar}\r\n {exportFileName && sorted.length > 0 && (\r\n <button\r\n onClick={handleExport}\r\n className=\"appearance-none cursor-pointer inline-flex items-center gap-1.5 h-8 px-3.5 rounded-lg border border-proef-navy dark:border-proef-turquoise bg-proef-navy dark:bg-proef-turquoise text-white dark:text-proef-navy hover:bg-proef-blue dark:hover:bg-proef-turquoise/80 text-[11px] font-black uppercase tracking-widest transition-colors\"\r\n >\r\n ⭳ {L.export}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Chips do agrupamento. */}\r\n {isGrouped && (\r\n <div className=\"mb-2 flex items-center gap-2 flex-wrap\">\r\n {groupByKeys.map((key) => {\r\n const col = columns.find((c) => c.key === key)\r\n return (\r\n <div\r\n key={key}\r\n className=\"flex items-center gap-1.5 bg-violet-100 dark:bg-violet-500/20 text-violet-800 dark:text-violet-200 px-2.5 py-1 text-[10px] font-black uppercase tracking-wider\"\r\n >\r\n <Layers size={9} />\r\n <span>{col?.label ?? key}</span>\r\n <button\r\n onClick={() => removerGrupo(key)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer ml-0.5 hover:text-violet-600\"\r\n >\r\n ✕\r\n </button>\r\n </div>\r\n )\r\n })}\r\n <button\r\n onClick={limparGrupos}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer text-[10px] font-black uppercase tracking-wider text-slate-400 hover:text-red-500 transition-colors\"\r\n >\r\n {L.clearGroups}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Chips dos filtros ativos. */}\r\n {(activeFilters.length > 0 || activeRanges.length > 0) && (\r\n <div className=\"mb-2 flex items-center gap-2 flex-wrap\">\r\n {activeRanges.map(([key, r]) => {\r\n const col = columns.find((c) => c.key === key)\r\n const fmt = (v?: number) =>\r\n v == null ? '' : col?.sortType === 'date' ? new Date(v).toLocaleDateString() : String(v)\r\n const txt =\r\n r.min != null && r.max != null\r\n ? `${fmt(r.min)} – ${fmt(r.max)}`\r\n : r.min != null\r\n ? `≥ ${fmt(r.min)}`\r\n : `≤ ${fmt(r.max)}`\r\n return (\r\n <div\r\n key={`r-${key}`}\r\n className=\"flex items-center gap-1.5 bg-proef-turquoise/15 text-proef-navy dark:text-proef-turquoise px-2.5 py-1 text-[10px] font-black uppercase tracking-wider\"\r\n >\r\n <span>\r\n {col?.label ?? key}: {txt}\r\n </span>\r\n <button\r\n onClick={() => clearColumnFilter(key)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer ml-0.5 hover:text-proef-blue dark:hover:text-proef-turquoise\"\r\n >\r\n ✕\r\n </button>\r\n </div>\r\n )\r\n })}\r\n {activeFilters.flatMap(([key, sel]) => {\r\n const col = columns.find((c) => c.key === key)\r\n const extra = !col ? columns.flatMap((c) => c.extraFilters ?? []).find((f) => f.key === key) : undefined\r\n const chipLabel = col?.label ?? extra?.label ?? key\r\n const arr = Array.from(sel)\r\n\r\n // Coluna com cores por valor: um chip colorido por valor, removível à parte.\r\n if (col?.valueChipColors) {\r\n const cores = col.valueChipColors\r\n return arr.map((v) => (\r\n <div\r\n key={`${key}:${v}`}\r\n className={`flex items-center gap-1.5 px-2.5 py-1 text-[10px] font-black uppercase tracking-wider ${\r\n cores[v] ?? 'bg-proef-turquoise/15 text-proef-navy dark:text-proef-turquoise'\r\n }`}\r\n >\r\n <span>\r\n {chipLabel}: {v}\r\n </span>\r\n <button\r\n onClick={() => toggleFilterValue(key, v)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer ml-0.5 hover:opacity-70\"\r\n >\r\n ✕\r\n </button>\r\n </div>\r\n ))\r\n }\r\n\r\n // Com 0 marcados o chip tem de dizer \"0\" — juntar uma lista vazia dava um chip mudo.\r\n const txt =\r\n arr.length > 2 || arr.length === 0\r\n ? L.selected.replace('{count}', String(arr.length))\r\n : arr.join(', ')\r\n return [\r\n <div\r\n key={key}\r\n className=\"flex items-center gap-1.5 bg-proef-turquoise/15 text-proef-navy dark:text-proef-turquoise px-2.5 py-1 text-[10px] font-black uppercase tracking-wider\"\r\n >\r\n <span>\r\n {chipLabel}: {txt}\r\n </span>\r\n <button\r\n onClick={() => clearColumnFilter(key)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer ml-0.5 hover:text-proef-blue dark:hover:text-proef-turquoise\"\r\n >\r\n ✕\r\n </button>\r\n </div>,\r\n ]\r\n })}\r\n <button\r\n onClick={clearAllFilters}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer text-[10px] font-black uppercase tracking-wider text-slate-400 hover:text-red-500 transition-colors\"\r\n >\r\n {L.clearAll}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* rounded-xl: os mesmos cantos dos cartões da biblioteca (Ui.tsx) e das tabelas dos portais. */}\r\n <div className=\"overflow-auto rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900\" style={{ maxHeight }}>\r\n <table className=\"w-full text-[13px] border-collapse dark:text-slate-200\">\r\n <thead className=\"sticky top-0 z-[2]\">\r\n <tr>\r\n {selectable && (\r\n <th className=\"bg-slate-50 dark:bg-slate-800 px-3 py-2.5 border-b border-slate-200 dark:border-slate-700 w-10 sticky left-0 z-[3]\">\r\n <button\r\n onClick={alternarTodas}\r\n className={`appearance-none cursor-pointer w-4 h-4 flex items-center justify-center border transition-colors rounded ${\r\n todasVisiveisMarcadas\r\n ? 'bg-proef-navy dark:bg-proef-turquoise border-proef-navy dark:border-proef-turquoise text-white dark:text-proef-navy'\r\n : algumaVisivelMarcada\r\n ? 'bg-slate-300 dark:bg-slate-600 border-slate-300 dark:border-slate-600 text-white'\r\n : 'bg-white dark:bg-slate-900 border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500'\r\n }`}\r\n >\r\n {(todasVisiveisMarcadas || algumaVisivelMarcada) && <span className=\"text-[9px] leading-none\">✓</span>}\r\n </button>\r\n </th>\r\n )}\r\n {columns.map((c) => {\r\n const isFiltered = temFiltro(c.key)\r\n const podeOrdenar = c.sortable !== false\r\n const podeAgrupar = agrupavel(c)\r\n const agrupada = groupByKeys.includes(c.key)\r\n return (\r\n <th\r\n key={c.key}\r\n style={{\r\n ...(c.width ? { minWidth: c.width } : {}),\r\n ...(larguras[c.key] ? { width: larguras[c.key], minWidth: larguras[c.key], maxWidth: larguras[c.key] } : {}),\r\n ...(estiloPreso(c) ?? {}),\r\n }}\r\n className={`group/th relative bg-slate-50 dark:bg-slate-800 text-[11.5px] uppercase tracking-wide text-slate-500 dark:text-slate-400 font-semibold px-3 py-2.5 border-b border-slate-200 dark:border-slate-700 whitespace-nowrap select-none ${alignClass(\r\n c.align\r\n )} ${c.sticky ? `sticky z-[3] bg-slate-50 dark:bg-slate-800 ${c.sticky === 'right' ? 'border-l border-slate-200 dark:border-slate-700' : ''}` : ''} ${c.headerClassName ?? ''}`}\r\n >\r\n {resizableColumns && (\r\n // Pega no limite direito: arrastar redimensiona; duplo clique repõe a largura automática\r\n <span\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n title={L.resizeColumn}\r\n onMouseDown={(e) => iniciarResize(e, c.key)}\r\n onDoubleClick={() => reporLargura(c.key)}\r\n onClick={(e) => e.stopPropagation()}\r\n className=\"absolute right-0 top-0 z-[4] h-full w-1.5 cursor-col-resize bg-transparent hover:bg-proef-blue/40 group-hover/th:bg-slate-300/60 dark:group-hover/th:bg-slate-600/60\"\r\n />\r\n )}\r\n <div className=\"flex items-center gap-1.5\">\r\n {podeOrdenar ? (\r\n <button\r\n onClick={() => toggleSort(c.key)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer p-0 uppercase tracking-wide text-slate-500 dark:text-slate-400 hover:text-proef-blue dark:hover:text-proef-turquoise font-semibold\"\r\n >\r\n {c.label}\r\n {sort?.key === c.key && <span className=\"ml-1\">{sort.dir === 1 ? '▲' : '▼'}</span>}\r\n </button>\r\n ) : (\r\n <span>{c.label}</span>\r\n )}\r\n\r\n {podeAgrupar && (\r\n <button\r\n onClick={() => alternarGrupo(c.key)}\r\n title={(agrupada ? L.ungroup : L.groupBy).replace('{label}', c.label)}\r\n className={`appearance-none bg-transparent border-0 cursor-pointer p-0.5 transition-colors ${\r\n agrupada ? 'text-violet-600 dark:text-violet-300' : 'text-slate-300 dark:text-slate-600 hover:text-slate-500 dark:hover:text-slate-300'\r\n }`}\r\n >\r\n <Layers size={10} />\r\n </button>\r\n )}\r\n\r\n {filtravel(c) && botaoFunil(c.key, c)}\r\n\r\n {c.extraFilters?.map((ef) => botaoFunil(ef.key, undefined, { label: ef.label }))}\r\n </div>\r\n {c.headerExtra && (\r\n <div className=\"mt-0.5 normal-case font-normal tracking-normal\">{c.headerExtra(filtered)}</div>\r\n )}\r\n </th>\r\n )\r\n })}\r\n </tr>\r\n </thead>\r\n <tbody>\r\n {aEsperarDataset ? (\r\n <tr>\r\n <td colSpan={nColunas} className=\"py-16 text-center text-sm font-bold uppercase tracking-widest text-slate-400\">\r\n {L.loading}\r\n </td>\r\n </tr>\r\n ) : isGrouped ? (\r\n grupos.length === 0 ? (\r\n <tr>\r\n <td colSpan={nColunas} className=\"py-16 text-center text-sm text-slate-400\">\r\n {L.empty}\r\n </td>\r\n </tr>\r\n ) : (\r\n renderGrupos(grupos, 0)\r\n )\r\n ) : sorted.length === 0 ? (\r\n <tr>\r\n <td colSpan={nColunas} className=\"py-16 text-center\">\r\n {emptyIcon && <div className=\"flex justify-center mb-2\">{emptyIcon}</div>}\r\n <p className=\"text-sm text-slate-400\">{L.empty}</p>\r\n {emptySubLabel && <p className=\"text-xs text-slate-400 mt-1\">{emptySubLabel}</p>}\r\n </td>\r\n </tr>\r\n ) : (\r\n pageData.map((row, i) => renderLinha(row, i))\r\n )}\r\n </tbody>\r\n {totalRow && !aEsperarDataset && sorted.length > 0 && (\r\n <tfoot className=\"sticky bottom-0 z-[2]\">\r\n <tr className=\"bg-slate-100 dark:bg-slate-800 font-black text-slate-800 dark:text-slate-100\">\r\n <td colSpan={colunasRotuloGrupo} className=\"px-3 py-2 border-t-2 border-slate-200 dark:border-slate-700 text-[10px] uppercase tracking-widest\">\r\n {L.total}\r\n </td>\r\n {colunasComValor.map((c) => (\r\n <td key={c.key} className={`px-3 py-2 border-t-2 border-slate-200 dark:border-slate-700 whitespace-nowrap text-xs ${alignClass(c.align)}`}>\r\n {c.groupAggregate?.(sorted)}\r\n </td>\r\n ))}\r\n </tr>\r\n </tfoot>\r\n )}\r\n </table>\r\n </div>\r\n\r\n {paginando && (totalPages > 1 || (pageSizeOptions?.length ?? 0) > 0) && (\r\n <div className=\"mt-2.5 flex items-center justify-between gap-2\">\r\n <span className=\"text-[10px] font-black uppercase tracking-widest text-slate-400\">\r\n {L.page.replace('{page}', String(currentPage)).replace('{pages}', String(totalPages))}\r\n </span>\r\n <div className=\"flex items-center gap-1\">\r\n {(pageSizeOptions?.length ?? 0) > 0 && (\r\n <span className=\"flex items-center gap-1.5 mr-2 text-[10px] font-black uppercase tracking-widest text-slate-400\">\r\n {L.perPage}\r\n {/* Mesma lista suspensa dos portais (Escolha), não o <select> nativo. */}\r\n <Escolha\r\n rotulo={L.perPage}\r\n valor={String(pageSizeAtual)}\r\n onMudar={(v) => setPageSizeAtual(Number(v))}\r\n className=\"w-20 normal-case tracking-normal font-normal\"\r\n opcoes={[...new Set([...(pageSizeOptions ?? []), pageSizeAtual!])].sort((a, b) => a - b).map(String)}\r\n />\r\n </span>\r\n )}\r\n <button\r\n disabled={currentPage <= 1}\r\n onClick={() => setPage((p) => Math.max(1, p - 1))}\r\n className=\"appearance-none cursor-pointer bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-[10px] font-black uppercase rounded-lg tracking-widest px-3 py-1.5 text-slate-500 dark:text-slate-400 hover:enabled:border-proef-navy dark:hover:enabled:border-proef-turquoise hover:enabled:text-proef-navy dark:hover:enabled:text-proef-turquoise disabled:opacity-30 disabled:cursor-not-allowed transition-colors\"\r\n >\r\n {L.prev}\r\n </button>\r\n <button\r\n disabled={currentPage >= totalPages}\r\n onClick={() => setPage((p) => Math.min(totalPages, p + 1))}\r\n className=\"appearance-none cursor-pointer bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-[10px] font-black uppercase rounded-lg tracking-widest px-3 py-1.5 text-slate-500 dark:text-slate-400 hover:enabled:border-proef-navy dark:hover:enabled:border-proef-turquoise hover:enabled:text-proef-navy dark:hover:enabled:text-proef-turquoise disabled:opacity-30 disabled:cursor-not-allowed transition-colors\"\r\n >\r\n {L.next}\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"names":["v"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataTable.js","sources":["../../../../../../src/legacy/components/molecules/DataTable/DataTable.tsx"],"sourcesContent":["import React, { Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'\r\nimport Escolha from '../../ui/Escolha'\r\nimport { createPortal } from 'react-dom'\r\n\r\n// ─── Tipos ──────────────────────────────────────────────────────────────────\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface DataTableColumn<T> {\r\n key: string\r\n label: string\r\n /** Valor usado p/ ordenar, filtrar e exportar. Sem isto usa-se row[key]. */\r\n value?: (row: T) => string | number | null | undefined\r\n /** Conteúdo apresentado. Sem isto mostra o value. */\r\n render?: (row: T) => React.ReactNode\r\n sortType?: 'text' | 'number' | 'date'\r\n /**\r\n * Fixa a coluna no scroll horizontal (position: sticky).\r\n * `true` ou `'left'` prende à esquerda; `'right'` prende à direita — útil para\r\n * a coluna de ações, que de outro modo fica fora de vista (e com os botões\r\n * cortados) assim que a tabela e mais larga do que o espaço disponível.\r\n */\r\n sticky?: boolean | 'left' | 'right'\r\n align?: 'left' | 'right' | 'center'\r\n /** minWidth em px. */\r\n width?: number\r\n /** Largura máxima em px (o conteúdo corta com \"…\" e mostra o valor completo no tooltip). Sobrepõe-se a `defaultMaxColumnWidth`. */\r\n maxWidth?: number\r\n /**\r\n * Filtro por coluna (distinct dos valores, multi-seleção; intervalo nas colunas number/date).\r\n * Por omissão LIGADO em todas as colunas com dados — `false` desliga. Colunas sem dados (ex.: ações) não o mostram.\r\n */\r\n filterable?: boolean\r\n /** Desliga a ordenação nesta coluna (default: ordenável). */\r\n sortable?: boolean\r\n /**\r\n * Agrupar por esta coluna. Por omissão LIGADO nas colunas de texto com dados e DESLIGADO nas de valor contínuo\r\n * (`sortType` number/date, ou com `groupAggregate`), onde cada linha daria o seu próprio grupo. `true`/`false` forçam.\r\n */\r\n groupable?: boolean\r\n /** Chave usada só para ORDENAR os grupos (sobrepõe-se a `value` ao agrupar por esta coluna). */\r\n groupSortKey?: (row: T) => string | number | null | undefined\r\n /**\r\n * Filtros extra desenhados como funis adicionais no cabeçalho desta coluna — para filtrar\r\n * por um campo que não tem coluna própria (ex: filtrar a coluna \"Obra\" por concelho).\r\n */\r\n extraFilters?: { key: string; label: string; value: (row: T) => string | number | null | undefined }[]\r\n headerClassName?: string\r\n cellClassName?: string\r\n /** Conteúdo extra por baixo do título da coluna (ex: somatório). Recebe as linhas já filtradas. */\r\n headerExtra?: (rows: T[]) => React.ReactNode\r\n /**\r\n * Valor desta coluna na linha de cada grupo (ex: soma em €), alinhado com a coluna. Recebe as linhas do grupo\r\n * (já filtradas). Também é usado na linha de total (`totalRow`), com todas as linhas filtradas.\r\n */\r\n groupAggregate?: (rows: T[]) => React.ReactNode\r\n /**\r\n * Cores por valor para os chips de filtro ativo (valor → classes tailwind). Com isto, cada\r\n * valor escolhido ganha o seu chip, removível à parte.\r\n */\r\n valueChipColors?: Record<string, string>\r\n}\r\n\r\n/**\r\n * Ordenação, filtros e agrupamento do cabeçalho vivem dentro da tabela. Quem está por fora\r\n * (ex: exportar para Excel, guardar uma vista) precisa deles — daí este handle, preenchido\r\n * em `viewRef`.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface DataTableView<T> {\r\n /** Aplica ordenação + filtros de coluna a qualquer lista (ex: ao dataset completo). */\r\n apply: (rows: T[]) => T[]\r\n /** Linhas à vista, já filtradas e ordenadas (todas as páginas). */\r\n rows: T[]\r\n hasFilters: boolean\r\n /**\r\n * Há filtros, ordenação ou agrupamento ativos — tudo do lado do cliente. Quem alimenta a\r\n * tabela usa isto para não largar o dataset completo: sem ele, filtrar sobre a página dá\r\n * uma lista vazia com o total nos milhares.\r\n */\r\n precisaDataset: boolean\r\n /** Estado guardável da vista. */\r\n estado: () => DataTableEstado\r\n /** Repõe um estado guardado (ex: ao aplicar uma vista do utilizador). */\r\n repor: (e: DataTableEstado | undefined) => void\r\n}\r\n\r\n/**\r\n * O que uma vista guardada leva da tabela. Sets viram arrays porque isto é serializado\r\n * para JSON.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport interface DataTableEstado {\r\n columnFilters?: Record<string, string[]>\r\n rangeFilters?: Record<string, { min?: number; max?: number }>\r\n sortKey?: string | null\r\n sortDir?: 'asc' | 'desc'\r\n /** Colunas por que se agrupa, pela ordem dos níveis. */\r\n groupBy?: string[]\r\n}\r\n\r\n/** @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy. */\r\nexport interface DataTableLabels {\r\n /** {count} é substituído pelo total (após filtros). */\r\n results: string\r\n /** Tooltip da pega de redimensionar coluna. */\r\n resizeColumn?: string\r\n export: string\r\n loading: string\r\n empty: string\r\n error: string\r\n search: string\r\n clear: string\r\n clearAll: string\r\n noOptions: string\r\n /** {count} = nº de valores selecionados (quando > 2, mostra \"N sel.\"). */\r\n selected: string\r\n /** {shown}/{total} — aparece quando o distinct da coluna tem mais valores do que os mostrados. */\r\n moreOptions: string\r\n /** Aviso no popup enquanto `allData` não chegou — as opções ainda são só as de `data`. */\r\n loadingOptions: string\r\n /** Placeholders dos dois campos do filtro por intervalo (colunas numéricas e de datas). */\r\n min: string\r\n max: string\r\n /** {page}/{pages} são substituídos. */\r\n page: string\r\n prev: string\r\n next: string\r\n /** Ordenação a partir do popup de filtro. */\r\n sortAsc: string\r\n sortDesc: string\r\n /** Agrupamento. {label} é o nome da coluna. */\r\n groupBy: string\r\n ungroup: string\r\n clearGroups: string\r\n /** Selector de linhas por página (só com `pageSizeOptions`). */\r\n perPage: string\r\n /** `filterMode: 'excel'`: primeira linha da lista de valores, o \"(Selecionar tudo)\" do Excel. */\r\n selectAll: string\r\n /** O mesmo, com pesquisa activa — aí só abrange os valores que correspondem. */\r\n selectAllResults: string\r\n /** Rótulo da linha de total (`totalRow`). */\r\n total: string\r\n}\r\n\r\nconst DEFAULT_LABELS: DataTableLabels = {\r\n total: 'Total',\r\n results: '{count} results',\r\n resizeColumn: 'Drag to resize · double-click to reset',\r\n export: 'Export',\r\n loading: 'Loading…',\r\n empty: 'No data',\r\n error: 'Failed to load',\r\n search: 'Search…',\r\n clear: 'Clear',\r\n clearAll: 'Clear all',\r\n noOptions: 'No options',\r\n selected: '{count} sel.',\r\n moreOptions: 'Showing {shown} of {total} — refine your search',\r\n loadingOptions: 'Loading options from all pages…',\r\n min: 'From',\r\n max: 'To',\r\n page: 'Page {page}/{pages}',\r\n prev: 'Prev',\r\n next: 'Next',\r\n sortAsc: 'Sort ascending',\r\n sortDesc: 'Sort descending',\r\n groupBy: 'Group by {label}',\r\n ungroup: 'Remove grouping by {label}',\r\n clearGroups: 'Clear grouping',\r\n perPage: 'Per page',\r\n selectAll: '(Select all)',\r\n selectAllResults: '(Select results)',\r\n}\r\n\r\n// ─── Ícones (inline, sem dependências) ───────────────────────────────────────\r\n\r\nconst Funnel = ({ size = 12, className }: { size?: number; className?: string }) => (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={className}>\r\n <path d=\"M22 3H2l8 9.46V19l4 2v-8.54L22 3z\" />\r\n </svg>\r\n)\r\n\r\nconst Layers = ({ size = 11, className }: { size?: number; className?: string }) => (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={className}>\r\n <path d=\"m12 2 9 5-9 5-9-5 9-5z\" />\r\n <path d=\"m3 17 9 5 9-5M3 12l9 5 9-5\" />\r\n </svg>\r\n)\r\n\r\nconst Caret = ({ size = 11, className }: { size?: number; className?: string }) => (\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" strokeLinejoin=\"round\" className={className}>\r\n <path d=\"m9 18 6-6-6-6\" />\r\n </svg>\r\n)\r\n\r\ninterface DataTableProps<T> {\r\n columns: DataTableColumn<T>[]\r\n data: T[]\r\n /**\r\n * Dataset completo, para o distinct dos filtros e para filtrar/ordenar/agrupar sobre ele.\r\n *\r\n * Sem isto a tabela só conhece `data` — e numa app que pagina no servidor isso são as\r\n * linhas da página, pelo que o funil de cada coluna listava só esses valores (e filtrar\r\n * por um valor que só existe noutra página dava zero linhas).\r\n */\r\n allData?: T[]\r\n /**\r\n * Chamado quando a tabela precisa de `allData` e ainda não o tem: ao abrir um funil, ao\r\n * ordenar, ao agrupar, ou quando já há filtros ativos. É o sinal para a app ir buscar o\r\n * dataset completo — uma vez, e só se alguém mexer.\r\n */\r\n onDataNeeded?: () => void\r\n /** `allData` a caminho — mostra um aviso no popup em vez de opções silenciosamente incompletas. */\r\n isAllDataLoading?: boolean\r\n /** Força o modo cliente mesmo sem interação (desde que `allData` exista). */\r\n alwaysClient?: boolean\r\n /**\r\n * Avisa quando a tabela passa (ou deixa) de trabalhar sobre `allData`. Quem tem paginação\r\n * de servidor por fora usa isto para a esconder: em modo cliente as linhas à vista já não\r\n * são a página que esse controlo diz estar a mostrar.\r\n */\r\n onClientModeChange?: (active: boolean) => void\r\n /** Chave estável por linha. Obrigatória para a seleção; sem ela usa-se o índice. */\r\n rowKey?: (row: T, index: number) => string | number\r\n onRowClick?: (row: T) => void\r\n /** Duplo clique — atalho para o detalhe sem roubar o clique simples (seleção, edição inline). */\r\n onRowDoubleClick?: (row: T) => void\r\n /**\r\n * Classes extra por linha, a partir dos dados — estado de risco, linha desactivada, etc.\r\n * Vem depois das classes da tabela, por isso ganha em caso de conflito.\r\n */\r\n rowClassName?: (row: T) => string | undefined\r\n /**\r\n * Chamado quando as linhas à vista mudam (filtros do cabeçalho, ordenação ou dados), com todas as páginas.\r\n * Serve para indicadores fora da tabela (ex.: cartões no topo da página) acompanharem os filtros.\r\n */\r\n onVisibleRowsChange?: (rows: T[]) => void\r\n isLoading?: boolean\r\n isError?: boolean\r\n /** max-height do viewport com scroll (default 70vh). */\r\n maxHeight?: string\r\n /** Linhas por página. Sem isto, mostra tudo (pode ser lento com muitas linhas). */\r\n pageSize?: number\r\n /** Mostra um selector de linhas por página com estas opções. */\r\n pageSizeOptions?: number[]\r\n /** Caixa de seleção por linha + no cabeçalho. */\r\n selectable?: boolean\r\n onSelectionChange?: (selectedKeys: Set<string>) => void\r\n /** Mudar este número limpa a seleção (ex: depois de uma ação em massa). */\r\n clearSelectionTrigger?: number\r\n /** Guarda filtros e intervalos no sessionStorage com esta chave (e as larguras das colunas no localStorage). */\r\n storageKey?: string\r\n /** Largura máxima por defeito das colunas, em px (default 360). `0` desliga o limite. */\r\n defaultMaxColumnWidth?: number\r\n /** Colunas redimensionáveis arrastando o limite do cabeçalho; duplo clique repõe (default true). */\r\n resizableColumns?: boolean\r\n /**\r\n * \"Ordenar A→Z / Z→A\" no topo do popup de filtro. Ligado por omissão (o cabeçalho é o mesmo em todos os\r\n * portais); `false` desliga. Os textos vêm de `labels.sortAsc`/`sortDesc` (por omissão em inglês).\r\n */\r\n sortFromFilter?: boolean\r\n /**\r\n * Semântica do filtro por coluna.\r\n *\r\n * - `'simple'`: abre com tudo **desmarcado** e marcar um valor quer dizer \"só este\".\r\n * Conjunto vazio = sem filtro.\r\n * - `'excel'` (default): abre com tudo **marcado** e desmarca-se o que se quer excluir, com uma linha\r\n * `(Selecionar tudo)` no topo e curingas (`*`, `?`, `~`) na pesquisa. É o que a folha de\r\n * cálculo faz, e o que quem vem de lá espera. Conjunto vazio = **nada seleccionado**, e a\r\n * tabela fica sem linhas.\r\n *\r\n * No modo Excel o distinto da coluna aberta deixa de respeitar os filtros das outras: o que a\r\n * lista mostra tem de ser exactamente o que a selecção materializa, senão marcar \"tudo\"\r\n * apagaria em silêncio os valores que outra coluna escondeu.\r\n */\r\n filterMode?: 'simple' | 'excel'\r\n /** Ordenação inicial. */\r\n defaultSortKey?: string\r\n defaultSortDir?: 'asc' | 'desc'\r\n /** Recebe o estado da vista (ordenação + filtros + agrupamento) para uso externo. */\r\n viewRef?: React.MutableRefObject<DataTableView<T> | null>\r\n /** Mostra o botão de exportar. Sem `onExport`, faz download CSV nativo. */\r\n exportFileName?: string\r\n /** Export custom (ex: XLSX na app). Recebe as linhas filtradas + ordenadas. */\r\n onExport?: (rows: T[]) => void\r\n /** Conteúdo extra na toolbar (à direita, antes do export). */\r\n extraToolbar?: React.ReactNode\r\n /** Texto por baixo do estado vazio. */\r\n emptySubLabel?: string\r\n /** Ilustração do estado vazio. */\r\n emptyIcon?: React.ReactNode\r\n labels?: Partial<DataTableLabels>\r\n /** Linha de total no fim (fica fixa em baixo), com o `groupAggregate` de cada coluna sobre as linhas filtradas. */\r\n totalRow?: boolean\r\n /** Agrupamento inicial (chaves de colunas, por ordem de nível). A pessoa pode mudá-lo depois. */\r\n defaultGroupBy?: string[]\r\n}\r\n\r\n// ─── Helpers ─────────────────────────────────────────────────────────────────\r\n\r\nconst EMPTY = '—'\r\n\r\n/** Nº máximo de opções renderizadas no popup de filtro por coluna — sem isto, uma coluna\r\n * quase-única por linha (ex: um ID) põe milhares de `<button>` no DOM de cada vez que o popup\r\n * abre, mesmo só visualmente cortado pelo scroll. A pesquisa continua a correr sobre o distinct\r\n * completo (memoizado), só o que é DESENHADO fica limitado. */\r\nconst MAX_OPCOES_FILTRO = 50\r\n\r\n/** Largura assumida p/ acumular o offset de colunas sticky sem `width` definido. */\r\nconst DEFAULT_STICKY_WIDTH = 160\r\n\r\n/** Collator único, reutilizado em todas as comparações de texto.\r\n * `a.localeCompare(b, undefined, { numeric: true })` constrói um `Intl.Collator` **a cada\r\n * chamada**; num sort de milhares de valores (ex: o distinct de uma coluna de IDs, com ~12k\r\n * valores ≈ 150k comparações) era isso, e não o render, que fazia o popup demorar a abrir. */\r\nconst COLLATOR = new Intl.Collator(undefined, { numeric: true })\r\n\r\nfunction rawValue<T>(row: T, col: DataTableColumn<T>): string | number | null {\r\n if (col.value) {\r\n const v = col.value(row)\r\n return v == null ? null : v\r\n }\r\n const v = (row as Record<string, unknown>)[col.key]\r\n if (v == null) return null\r\n if (typeof v === 'number' || typeof v === 'string') return v\r\n return String(v)\r\n}\r\n\r\n/** Valor normalizado p/ filtro (string; vazio → EMPTY). */\r\nfunction filterKey<T>(row: T, col: DataTableColumn<T>): string {\r\n const v = rawValue(row, col)\r\n return v == null || v === '' ? EMPTY : String(v)\r\n}\r\n\r\n/**\r\n * Timestamp de um valor de coluna de data. As apps passam a data já formatada (`value:\r\n * dataPt(...)` → \"14/08/2026\"), e `new Date(\"14/08/2026\")` devolve `Invalid Date` — ou, pior,\r\n * lê \"06/03/2026\" como 3 de junho, à americana. Daí o parse explícito de dd/mm/aaaa antes do\r\n * fallback para o `Date` nativo (que trata do ISO).\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport function timestampDeData(v: unknown): number {\r\n if (v == null || v === '') return NaN\r\n if (v instanceof Date) return v.getTime()\r\n const s = String(v).trim()\r\n const m = /^(\\d{1,2})[/-](\\d{1,2})[/-](\\d{4})/.exec(s)\r\n if (m) return Date.UTC(Number(m[3]), Number(m[2]) - 1, Number(m[1]))\r\n const t = new Date(s).getTime()\r\n return Number.isNaN(t) ? NaN : t\r\n}\r\n\r\nfunction compare(\r\n a: string | number | null,\r\n b: string | number | null,\r\n type: DataTableColumn<unknown>['sortType']\r\n): number {\r\n if (a == null && b == null) return 0\r\n if (a == null) return 1\r\n if (b == null) return -1\r\n if (type === 'number') return Number(a) - Number(b)\r\n if (type === 'date') return timestampDeData(a) - timestampDeData(b)\r\n return COLLATOR.compare(String(a), String(b))\r\n}\r\n\r\n/** Valor comparável de uma célula para o filtro por intervalo (número ou data). */\r\nfunction valorParaIntervalo<T>(row: T, col: DataTableColumn<T>): number {\r\n const v = rawValue(row, col)\r\n return col.sortType === 'date' ? timestampDeData(v) : Number(v)\r\n}\r\n\r\nfunction toCsv<T>(columns: DataTableColumn<T>[], rows: T[]): string {\r\n const esc = (v: unknown) => {\r\n const s = v == null ? '' : String(v)\r\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s\r\n }\r\n const head = columns.map((c) => esc(c.label)).join(',')\r\n const body = rows.map((r) => columns.map((c) => esc(rawValue(r, c))).join(',')).join('\\n')\r\n return `${head}\\n${body}`\r\n}\r\n\r\nconst ordenarRotulos = (a: string, b: string) => (a === EMPTY ? 1 : b === EMPTY ? -1 : COLLATOR.compare(a, b))\r\n\r\n/** Identidade estável para as dependências que o modo Excel não usa — ver `cruzFiltros`. */\r\nconst SEM_CRUZAMENTO: [string, never][] = []\r\n\r\nconst escaparRegex = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n\r\n/**\r\n * Predicado da caixa de pesquisa do filtro por coluna, com os curingas do Excel.\r\n *\r\n * `*` = qualquer sequência, `?` = exactamente um carácter, `~` escapa qualquer um dos três. Sem\r\n * curinga nenhum é \"contém\"; **com** curinga o padrão passa a ser comparado com o valor **inteiro**,\r\n * e é daí que sai o \"começa por\" (`25VCT*`), o \"termina em\" (`*.fbo`) e o \"tem isto no meio\"\r\n * (`*RA2*`).\r\n *\r\n * No Excel esse ancorar vive nos Filtros de Texto (Começa por/Termina em) e não na caixa de\r\n * pesquisa, que é sempre \"contém\" — juntar os dois na caixa evita ter de trazer também o submenu, e\r\n * sem curingas o comportamento é o mesmo dos dois lados.\r\n *\r\n * Tudo em minúsculas: quem chama passa a pesquisa e o valor já em minúsculas.\r\n */\r\nexport function pesquisaCuringa(q: string): (v: string) => boolean {\r\n let fonte = ''\r\n let literal = ''\r\n let temCuringa = false\r\n for (let i = 0; i < q.length; i++) {\r\n const c = q[i]!\r\n const seguinte = q[i + 1]\r\n if (c === '~' && (seguinte === '*' || seguinte === '?' || seguinte === '~')) {\r\n fonte += escaparRegex(seguinte)\r\n literal += seguinte\r\n i++\r\n continue\r\n }\r\n if (c === '*' || c === '?') {\r\n fonte += c === '*' ? '.*' : '.'\r\n literal += c\r\n temCuringa = true\r\n continue\r\n }\r\n fonte += escaparRegex(c)\r\n literal += c\r\n }\r\n // Sem curinga usa-se o literal (e não `q`), para `~*` procurar mesmo um asterisco.\r\n if (!temCuringa) return (v) => v.includes(literal)\r\n const rx = new RegExp(`^${fonte}$`)\r\n return (v) => rx.test(v)\r\n}\r\n\r\ninterface GrupoNo<T> {\r\n val: string\r\n ordem: string\r\n path: string\r\n items: T[]\r\n filhos: GrupoNo<T>[]\r\n}\r\n\r\n// ─── Componente ──────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Tabela genérica: ordenação, filtro por coluna com distinct (multi-seleção) e por intervalo,\r\n * agrupamento multi-nível, seleção de linhas, colunas fixas (sticky), align/width, export\r\n * (CSV nativo ou custom) e estados loading/vazio/erro. Sem dependências de ícones/i18n —\r\n * textos via `labels`.\r\n *\r\n * Com paginação de servidor, passa também `allData`/`onDataNeeded`: sem o dataset completo,\r\n * filtrar, ordenar e agrupar só olhariam para a página à vista.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport default function DataTable<T>({\r\n columns,\r\n data,\r\n allData,\r\n onDataNeeded,\r\n isAllDataLoading,\r\n alwaysClient,\r\n onClientModeChange,\r\n rowKey,\r\n onRowClick,\r\n onRowDoubleClick,\r\n rowClassName,\r\n onVisibleRowsChange,\r\n isLoading,\r\n isError,\r\n maxHeight = '70vh',\r\n pageSize,\r\n pageSizeOptions,\r\n selectable,\r\n onSelectionChange,\r\n clearSelectionTrigger,\r\n storageKey,\r\n defaultMaxColumnWidth = 360,\r\n resizableColumns = true,\r\n sortFromFilter = true,\r\n filterMode = 'excel',\r\n defaultSortKey,\r\n defaultSortDir,\r\n viewRef,\r\n exportFileName,\r\n onExport,\r\n extraToolbar,\r\n emptySubLabel,\r\n emptyIcon,\r\n labels,\r\n totalRow = false,\r\n defaultGroupBy,\r\n}: DataTableProps<T>) {\r\n const L = { ...DEFAULT_LABELS, ...labels }\r\n\r\n // Larguras definidas pela pessoa (arrastar o limite do cabeçalho). Guardadas por tabela no localStorage.\r\n const [larguras, setLarguras] = useState<Record<string, number>>(() => {\r\n if (!storageKey) return {}\r\n try {\r\n return JSON.parse(localStorage.getItem(`dt_widths_${storageKey}`) ?? '{}') as Record<string, number>\r\n } catch {\r\n return {}\r\n }\r\n })\r\n useEffect(() => {\r\n if (!storageKey) return\r\n try {\r\n localStorage.setItem(`dt_widths_${storageKey}`, JSON.stringify(larguras))\r\n } catch {\r\n /* storage indisponível */\r\n }\r\n }, [larguras, storageKey])\r\n const larguraMax = (c: DataTableColumn<T>) => larguras[c.key] ?? c.maxWidth ?? (defaultMaxColumnWidth > 0 ? defaultMaxColumnWidth : undefined)\r\n const iniciarResize = (e: React.MouseEvent, key: string) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n const th = (e.currentTarget as HTMLElement).parentElement\r\n const x0 = e.clientX\r\n const w0 = th?.getBoundingClientRect().width ?? 120\r\n const mover = (ev: MouseEvent) => setLarguras((l) => ({ ...l, [key]: Math.max(60, Math.round(w0 + ev.clientX - x0)) }))\r\n const parar = () => {\r\n window.removeEventListener('mousemove', mover)\r\n window.removeEventListener('mouseup', parar)\r\n document.body.style.cursor = ''\r\n }\r\n document.body.style.cursor = 'col-resize'\r\n window.addEventListener('mousemove', mover)\r\n window.addEventListener('mouseup', parar)\r\n }\r\n const reporLargura = (key: string) =>\r\n setLarguras((l) => {\r\n const n = { ...l }\r\n delete n[key]\r\n return n\r\n })\r\n // Com `storageKey`, a ordenação e o agrupamento ficam na sessão como os filtros: ao voltar à tabela (ex.: mudar\r\n // de separador e regressar) a vista é a mesma. Valor guardado (mesmo vazio) ganha aos de omissão.\r\n const lerSessao = <V,>(sufixo: string): V | undefined => {\r\n if (!storageKey) return undefined\r\n try {\r\n const raw = sessionStorage.getItem(`dt_${sufixo}_${storageKey}`)\r\n return raw === null ? undefined : (JSON.parse(raw) as V)\r\n } catch {\r\n return undefined\r\n }\r\n }\r\n const [sort, setSort] = useState<{ key: string; dir: 1 | -1 } | null>(() => {\r\n const guardada = lerSessao<{ key: string; dir: 1 | -1 } | null>('sort')\r\n if (guardada !== undefined) return guardada && columns.some((c) => c.key === guardada.key) ? guardada : null\r\n return defaultSortKey ? { key: defaultSortKey, dir: defaultSortDir === 'desc' ? -1 : 1 } : null\r\n })\r\n const [columnFilters, setColumnFilters] = useState<Record<string, Set<string>>>(() => {\r\n if (!storageKey) return {}\r\n try {\r\n const raw = sessionStorage.getItem(`dt_filters_${storageKey}`)\r\n if (!raw) return {}\r\n const parsed = JSON.parse(raw) as Record<string, string[]>\r\n return Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, new Set(v)]))\r\n } catch {\r\n return {}\r\n }\r\n })\r\n // Filtro por intervalo, em colunas numéricas e de datas (`sortType`). Numa coluna como\r\n // \"Dias Penalização\" a lista de valores distintos é inútil — ninguém procura \"88 valores\" um a\r\n // um, quer é \"acima de 30\". Convive com a seleção por valor: se as duas estiverem ativas, a\r\n // linha tem de passar nas duas.\r\n const [rangeFilters, setRangeFilters] = useState<Record<string, { min?: number; max?: number }>>(() => {\r\n if (!storageKey) return {}\r\n try {\r\n const raw = sessionStorage.getItem(`dt_ranges_${storageKey}`)\r\n return raw ? (JSON.parse(raw) as Record<string, { min?: number; max?: number }>) : {}\r\n } catch {\r\n return {}\r\n }\r\n })\r\n const [groupByKeys, setGroupByKeys] = useState<string[]>(() =>\r\n (lerSessao<string[]>('group') ?? defaultGroupBy ?? []).filter((k) => columns.some((c) => c.key === k))\r\n )\r\n const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(new Set())\r\n const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set())\r\n const [openFilter, setOpenFilter] = useState<string | null>(null)\r\n const [filterSearch, setFilterSearch] = useState('')\r\n const filterRefs = useRef<Map<string, HTMLDivElement>>(new Map())\r\n const popupRef = useRef<HTMLDivElement | null>(null)\r\n // O popup é desenhado num portal, não dentro do `<th>`: a tabela vive num contentor com\r\n // `overflow: auto` e, quando tem poucas linhas, esse contentor é baixo e cortava o popup ao\r\n // fim de dois ou três valores. Fora da árvore da tabela, com `position: fixed`, deixa de haver\r\n // nada que o corte.\r\n const [popupPos, setPopupPos] = useState<{ top: number; left: number } | null>(null)\r\n\r\n const isGrouped = groupByKeys.length > 0\r\n\r\n useEffect(() => {\r\n if (!storageKey) return\r\n const serializavel = Object.fromEntries(Object.entries(columnFilters).map(([k, v]) => [k, Array.from(v)]))\r\n try {\r\n sessionStorage.setItem(`dt_filters_${storageKey}`, JSON.stringify(serializavel))\r\n sessionStorage.setItem(`dt_ranges_${storageKey}`, JSON.stringify(rangeFilters))\r\n sessionStorage.setItem(`dt_sort_${storageKey}`, JSON.stringify(sort))\r\n sessionStorage.setItem(`dt_group_${storageKey}`, JSON.stringify(groupByKeys))\r\n } catch {\r\n /* sessionStorage indisponível (private mode) — a tabela funciona na mesma, só não recorda. */\r\n }\r\n }, [columnFilters, rangeFilters, sort, groupByKeys, storageKey])\r\n\r\n useLayoutEffect(() => {\r\n if (!openFilter) {\r\n setPopupPos(null)\r\n return\r\n }\r\n const recalcular = () => {\r\n const el = filterRefs.current.get(openFilter)\r\n if (!el) return\r\n const r = el.getBoundingClientRect()\r\n // Tem de acompanhar o `w-72` do popup — é o que o impede de ficar fora do ecrã à direita.\r\n const largura = Math.min(288, window.innerWidth * 0.8)\r\n // Encosta à direita quando a coluna está no fim da tabela e o popup passaria do ecrã.\r\n const left = Math.max(8, Math.min(r.left, window.innerWidth - largura - 8))\r\n setPopupPos({ top: r.bottom + 6, left })\r\n }\r\n recalcular()\r\n // `true` na captura: apanha também o scroll do contentor da tabela, que não borbulha.\r\n window.addEventListener('scroll', recalcular, true)\r\n window.addEventListener('resize', recalcular)\r\n return () => {\r\n window.removeEventListener('scroll', recalcular, true)\r\n window.removeEventListener('resize', recalcular)\r\n }\r\n }, [openFilter])\r\n\r\n // Fecha o dropdown de filtro em clique fora (o popup está fora do `<th>`, daí os dois testes).\r\n useEffect(() => {\r\n const onDown = (e: MouseEvent) => {\r\n if (!openFilter) return\r\n const alvo = e.target as Node\r\n const botao = filterRefs.current.get(openFilter)\r\n if (botao?.contains(alvo) || popupRef.current?.contains(alvo)) return\r\n setOpenFilter(null)\r\n setFilterSearch('')\r\n }\r\n document.addEventListener('mousedown', onDown)\r\n return () => document.removeEventListener('mousedown', onDown)\r\n }, [openFilter])\r\n\r\n /**\r\n * Função de valor por chave de filtro. A chave tanto pode ser uma coluna como um\r\n * `extraFilters` pendurado no cabeçalho de outra coluna.\r\n */\r\n const resolverValor = (key: string): ((row: T) => string | number | null | undefined) | undefined => {\r\n const col = columns.find((c) => c.key === key)\r\n if (col) return (row: T) => rawValue(row, col)\r\n for (const c of columns) {\r\n const ef = c.extraFilters?.find((f) => f.key === key)\r\n if (ef) return ef.value\r\n }\r\n return undefined\r\n }\r\n\r\n const rotuloDe = (fn: (row: T) => string | number | null | undefined, row: T) => {\r\n const v = fn(row)\r\n return v == null || v === '' ? EMPTY : String(v)\r\n }\r\n\r\n const modoExcel = filterMode === 'excel'\r\n\r\n /**\r\n * No modo Excel é a **presença da chave** que liga o filtro, e não o tamanho do conjunto: sem\r\n * chave = sem filtro = tudo marcado no popover (o estado em que o Excel abre). Com chave passam\r\n * só os valores do conjunto, e um conjunto vazio é \"nada seleccionado\" — a tabela fica sem\r\n * linhas, como no Excel quando se desmarca o `(Selecionar tudo)`.\r\n */\r\n const activeFilters = useMemo(\r\n () => Object.entries(columnFilters).filter(([, s]) => s && (modoExcel || s.size > 0)),\r\n [columnFilters, modoExcel]\r\n )\r\n const activeRanges = useMemo(\r\n () => Object.entries(rangeFilters).filter(([, r]) => r && (r.min != null || r.max != null)),\r\n [rangeFilters]\r\n )\r\n const hasActiveFilters = activeFilters.length > 0 || activeRanges.length > 0\r\n\r\n /**\r\n * Com o dataset completo em mão, é sobre ele que se filtra, ordena e agrupa — não sobre\r\n * `data`. Sem isto, escolher no funil um valor que só existe noutra página dava uma tabela\r\n * vazia: as opções vinham de todos os dados mas o filtro corria só sobre a página.\r\n */\r\n const isClientMode = !!allData && (!!alwaysClient || hasActiveFilters || !!sort || isGrouped)\r\n const effectiveData = isClientMode ? allData! : data\r\n\r\n // Pedir o dataset completo. Não só no arranque: os filtros podem vir do sessionStorage ou\r\n // de uma vista reposta, e quem alimenta a tabela pode largar o dataset a meio.\r\n useEffect(() => {\r\n if (!allData && (hasActiveFilters || sort || isGrouped)) onDataNeeded?.()\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [allData, hasActiveFilters, sort, isGrouped])\r\n\r\n useEffect(() => {\r\n onClientModeChange?.(isClientMode)\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [isClientMode])\r\n\r\n useEffect(() => {\r\n if (clearSelectionTrigger === undefined) return\r\n setSelectedKeys(new Set())\r\n }, [clearSelectionTrigger])\r\n\r\n /**\r\n * Filtrar pelo cabeçalho também limpa a seleção.\r\n *\r\n * O que estava marcado era-o daquela lista: filtrar para outra e manter as marcas deixava\r\n * escolhidas linhas que já não se veem — e quem age sobre a seleção (uma ação em massa)\r\n * agia sobre elas na mesma. Avisa-se o ecrã, e não só se limpa por dentro: as caixas\r\n * ficavam vazias e o botão de cima continuava a dizer \"Tratar 250\" — e tratava-as.\r\n *\r\n * Ordenar não conta: são as mesmas linhas por outra ordem.\r\n */\r\n const chaveFiltrosCol = JSON.stringify(\r\n Object.entries(columnFilters)\r\n .map(([k, v]) => [k, [...v].sort()])\r\n .sort()\r\n )\r\n useEffect(() => {\r\n if (!selectable) return\r\n setSelectedKeys(new Set())\r\n onSelectionChange?.(new Set())\r\n // `onSelectionChange` fora das dependências de propósito: é uma função do ecrã, recriada a\r\n // cada render, e segui-la limpava a seleção sozinha.\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [chaveFiltrosCol])\r\n\r\n /**\r\n * No modo Excel o distinto é o puro da coluna (ver `filterMode`), por isso não depende dos\r\n * filtros das outras. Fixar aqui a identidade evita recalcular a lista inteira a cada clique\r\n * numa caixa — que é precisamente o que uma coluna quase-única por linha não perdoa.\r\n */\r\n const cruzFiltros = modoExcel ? SEM_CRUZAMENTO : activeFilters\r\n const cruzRanges = modoExcel ? SEM_CRUZAMENTO : activeRanges\r\n\r\n /**\r\n * Distinct de uma chave, a partir do dataset completo quando ele existe: as opções do funil\r\n * são as da tabela toda, não as da página que calhou estar à vista.\r\n *\r\n * Os filtros das OUTRAS colunas aplicam-se (mas não o da própria): assim escolher \"Porto\"\r\n * numa coluna faz a coluna ao lado deixar de oferecer valores que já não existem — sem\r\n * impedir de alargar a escolha na coluna que se está a mexer.\r\n */\r\n const distinctValues = (key: string): string[] => {\r\n const valueFn = resolverValor(key)\r\n if (!valueFn) return []\r\n let fonte = allData ?? data\r\n for (const [outraKey, escolhidos] of cruzFiltros) {\r\n if (outraKey === key) continue\r\n const outraFn = resolverValor(outraKey)\r\n if (!outraFn) continue\r\n fonte = fonte.filter((row) => escolhidos.has(rotuloDe(outraFn, row)))\r\n }\r\n for (const [outraKey, r] of cruzRanges) {\r\n if (outraKey === key) continue\r\n const col = columns.find((c) => c.key === outraKey)\r\n if (!col) continue\r\n fonte = fonte.filter((row) => {\r\n const n = valorParaIntervalo(row, col)\r\n if (!Number.isFinite(n)) return false\r\n if (r.min != null && n < r.min) return false\r\n if (r.max != null && n > r.max) return false\r\n return true\r\n })\r\n }\r\n const set = new Set<string>()\r\n fonte.forEach((row) => set.add(rotuloDe(valueFn, row)))\r\n return Array.from(set).sort(ordenarRotulos)\r\n }\r\n\r\n // Memoizado ao abrir/mudar de coluna (não a cada tecla da pesquisa) — numa coluna quase-única\r\n // por linha, recalcular o distinct (iterar + Set + sort) em cada keystroke era o grosso da\r\n // lentidão do popup, não só a 1ª abertura.\r\n const openFilterOptions = useMemo(() => {\r\n if (!openFilter) return []\r\n return distinctValues(openFilter)\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [openFilter, data, allData, columns, cruzFiltros, cruzRanges])\r\n\r\n /** Versão em minúsculas do distinct, calculada uma vez por abertura — a pesquisa do popup\r\n * corria `v.toLowerCase()` sobre a lista inteira a cada tecla. */\r\n const openFilterOptionsLower = useMemo(() => openFilterOptions.map((v) => v.toLowerCase()), [openFilterOptions])\r\n\r\n /** Opções a desenhar (no máximo `MAX_OPCOES_FILTRO`) + total de correspondências, para o\r\n * rodapé \"a mostrar X de Y\". Um só varrimento, sem alocar o array completo dos matches. */\r\n const opcoesVisiveis = useMemo(() => {\r\n const q = filterSearch.toLowerCase()\r\n // No modo Excel a pesquisa aceita curingas; no simples mantém-se o \"contém\" de sempre.\r\n const corresponde = q ? (modoExcel ? pesquisaCuringa(q) : (v: string) => v.includes(q)) : null\r\n const opts: string[] = []\r\n // `correspondentes` é tudo o que a pesquisa apanha, não só o que se desenha: com 200\r\n // correspondências e 50 desenhadas, o `(Selecionar tudo)` tem de apanhar as 200 — é o que o\r\n // Excel faz, e o contrário seria uma armadilha silenciosa.\r\n const correspondentes: string[] = []\r\n for (let i = 0; i < openFilterOptions.length; i++) {\r\n if (corresponde && !corresponde(openFilterOptionsLower[i])) continue\r\n correspondentes.push(openFilterOptions[i])\r\n if (opts.length < MAX_OPCOES_FILTRO) opts.push(openFilterOptions[i])\r\n }\r\n return { opts, total: correspondentes.length, correspondentes }\r\n }, [openFilterOptions, openFilterOptionsLower, filterSearch, modoExcel])\r\n\r\n // Filtrar e ordenar são funções sobre uma lista qualquer (não só sobre a vista atual): é o\r\n // que permite reaplicar a mesma vista ao dataset completo — ver `viewRef`/`DataTableView`.\r\n const filtrar = (rows: T[]) => {\r\n if (activeFilters.length === 0 && activeRanges.length === 0) return rows\r\n return rows.filter(\r\n (row) =>\r\n activeFilters.every(([key, sel]) => {\r\n const fn = resolverValor(key)\r\n return fn ? sel.has(rotuloDe(fn, row)) : true\r\n }) &&\r\n activeRanges.every(([key, r]) => {\r\n const col = columns.find((c) => c.key === key)\r\n if (!col) return true\r\n const n = valorParaIntervalo(row, col)\r\n // Sem valor não passa num intervalo — \"maior que 30\" não inclui \"sem dias\", e um\r\n // intervalo de datas não inclui linhas sem data.\r\n if (!Number.isFinite(n)) return false\r\n if (r.min != null && n < r.min) return false\r\n if (r.max != null && n > r.max) return false\r\n return true\r\n })\r\n )\r\n }\r\n\r\n const ordenar = (rows: T[]) => {\r\n if (!sort) return rows\r\n const col = columns.find((c) => c.key === sort.key)\r\n if (!col) return rows\r\n return [...rows].sort((a, b) => compare(rawValue(a, col), rawValue(b, col), col.sortType) * sort.dir)\r\n }\r\n\r\n const filtered = useMemo(\r\n () => filtrar(effectiveData),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [effectiveData, activeFilters, activeRanges, columns]\r\n )\r\n\r\n const sorted = useMemo(\r\n () => ordenar(filtered),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [filtered, sort, columns]\r\n )\r\n\r\n // Ref para o aviso não depender da identidade da função (quem chama passa normalmente uma arrow nova).\r\n const avisarVisiveis = useRef(onVisibleRowsChange)\r\n avisarVisiveis.current = onVisibleRowsChange\r\n useEffect(() => {\r\n avisarVisiveis.current?.(sorted)\r\n }, [sorted])\r\n\r\n // ─── Agrupamento ───────────────────────────────────────────────────────────\r\n\r\n const alternarGrupo = (key: string) => {\r\n if (!allData) onDataNeeded?.()\r\n setGroupByKeys((prev) => (prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]))\r\n setCollapsedGroups(new Set())\r\n }\r\n const removerGrupo = (key: string) => {\r\n setGroupByKeys((prev) => prev.filter((k) => k !== key))\r\n setCollapsedGroups(new Set())\r\n }\r\n const limparGrupos = () => {\r\n setGroupByKeys([])\r\n setCollapsedGroups(new Set())\r\n }\r\n const alternarColapso = (path: string) =>\r\n setCollapsedGroups((prev) => {\r\n const n = new Set(prev)\r\n if (n.has(path)) n.delete(path)\r\n else n.add(path)\r\n return n\r\n })\r\n\r\n const construirGrupos = (items: T[], keys: string[], nivel: number, pathPai: string): GrupoNo<T>[] => {\r\n if (nivel >= keys.length) return []\r\n const key = keys[nivel]\r\n const col = columns.find((c) => c.key === key)\r\n if (!col) return []\r\n // `groupSortKey` manda na ORDEM dos grupos; o rótulo continua a ser o valor da coluna.\r\n const ordemFn = col.groupSortKey\r\n const mapa = new Map<string, { ordem: string; items: T[] }>()\r\n items.forEach((item) => {\r\n const rotulo = filterKey(item, col)\r\n const ordem = ordemFn ? String(ordemFn(item) ?? '') : rotulo\r\n const atual = mapa.get(rotulo)\r\n if (atual) atual.items.push(item)\r\n else mapa.set(rotulo, { ordem, items: [item] })\r\n })\r\n return Array.from(mapa.entries())\r\n .sort(([ra, a], [rb, b]) => (ordemFn ? COLLATOR.compare(a.ordem, b.ordem) : ordenarRotulos(ra, rb)))\r\n .map(([val, { ordem, items: itensGrupo }]) => {\r\n const path = pathPai ? `${pathPai}\\u0000${val}` : val\r\n return { val, ordem, path, items: itensGrupo, filhos: construirGrupos(itensGrupo, keys, nivel + 1, path) }\r\n })\r\n }\r\n\r\n const grupos = useMemo(\r\n () => (isGrouped ? construirGrupos(sorted, groupByKeys, 0, '') : []),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [sorted, groupByKeys, isGrouped, columns]\r\n )\r\n\r\n // ─── Paginação (desligada ao agrupar) ──────────────────────────────────────\r\n\r\n const [page, setPage] = useState(1)\r\n const [pageSizeAtual, setPageSizeAtual] = useState<number | undefined>(pageSize)\r\n useEffect(() => setPageSizeAtual(pageSize), [pageSize])\r\n useEffect(() => {\r\n setPage(1)\r\n }, [columnFilters, rangeFilters, sort, groupByKeys, pageSizeAtual])\r\n const paginando = !!pageSizeAtual && !isGrouped\r\n const totalPages = paginando ? Math.max(1, Math.ceil(sorted.length / pageSizeAtual!)) : 1\r\n const currentPage = Math.min(page, totalPages)\r\n const pageData = paginando ? sorted.slice((currentPage - 1) * pageSizeAtual!, currentPage * pageSizeAtual!) : sorted\r\n\r\n // ─── Seleção ───────────────────────────────────────────────────────────────\r\n\r\n const chaveDe = (row: T, i: number) => String(rowKey ? rowKey(row, i) : i)\r\n\r\n /**\r\n * Índice de cada linha na lista completa. Ao agrupar, as linhas são desenhadas grupo a\r\n * grupo e o índice local recomeçava em 0 em cada um — o que dava chaves React repetidas,\r\n * a zebra a reiniciar e, sem `rowKey`, linhas de grupos diferentes a partilhar a mesma\r\n * chave de seleção.\r\n */\r\n const indicePorLinha = useMemo(() => {\r\n const m = new Map<T, number>()\r\n sorted.forEach((r, i) => m.set(r, i))\r\n return m\r\n }, [sorted])\r\n const visibleKeys = useMemo(\r\n () => pageData.map((row, i) => chaveDe(row, i)),\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n [pageData, rowKey]\r\n )\r\n const alternarLinha = (key: string) =>\r\n setSelectedKeys((prev) => {\r\n const next = new Set(prev)\r\n if (next.has(key)) next.delete(key)\r\n else next.add(key)\r\n onSelectionChange?.(next)\r\n return next\r\n })\r\n const alternarTodas = () =>\r\n setSelectedKeys((prev) => {\r\n const todasMarcadas = visibleKeys.length > 0 && visibleKeys.every((k) => prev.has(k))\r\n const next = new Set(prev)\r\n if (todasMarcadas) visibleKeys.forEach((k) => next.delete(k))\r\n else visibleKeys.forEach((k) => next.add(k))\r\n onSelectionChange?.(next)\r\n return next\r\n })\r\n const todasVisiveisMarcadas = visibleKeys.length > 0 && visibleKeys.every((k) => selectedKeys.has(k))\r\n const algumaVisivelMarcada = visibleKeys.some((k) => selectedKeys.has(k)) && !todasVisiveisMarcadas\r\n\r\n // ─── Handle externo da vista ───────────────────────────────────────────────\r\n\r\n // Sem deps: é lido de fora a pedido (ex: ao exportar), tem de estar sempre coerente com o\r\n // último render.\r\n useEffect(() => {\r\n if (!viewRef) return\r\n viewRef.current = {\r\n apply: (rows) => ordenar(filtrar(rows)),\r\n rows: sorted,\r\n hasFilters: hasActiveFilters,\r\n precisaDataset: hasActiveFilters || !!sort || isGrouped,\r\n estado: () => ({\r\n columnFilters: Object.fromEntries(activeFilters.map(([k, v]) => [k, [...v]])),\r\n rangeFilters: Object.fromEntries(activeRanges),\r\n sortKey: sort?.key ?? null,\r\n sortDir: sort ? (sort.dir === 1 ? 'asc' : 'desc') : 'asc',\r\n groupBy: groupByKeys,\r\n }),\r\n repor: (e) => {\r\n setColumnFilters(Object.fromEntries(Object.entries(e?.columnFilters ?? {}).map(([k, v]) => [k, new Set(v)])))\r\n setRangeFilters(e?.rangeFilters ?? {})\r\n setSort(e?.sortKey ? { key: e.sortKey, dir: e.sortDir === 'desc' ? -1 : 1 } : null)\r\n setGroupByKeys(e?.groupBy ?? [])\r\n // Grupos recolhidos não se guardam: a vista diz por que se agrupa, não o que estava\r\n // aberto no momento em que foi gravada.\r\n setCollapsedGroups(new Set())\r\n const precisaTudo =\r\n !!e?.sortKey ||\r\n !!e?.groupBy?.length ||\r\n Object.keys(e?.columnFilters ?? {}).length > 0 ||\r\n Object.keys(e?.rangeFilters ?? {}).length > 0\r\n if (precisaTudo && !allData) onDataNeeded?.()\r\n },\r\n }\r\n })\r\n\r\n // ─── Ações de filtro/ordenação ─────────────────────────────────────────────\r\n\r\n const toggleSort = (key: string) => {\r\n if (!allData) onDataNeeded?.()\r\n setSort((s) => (s?.key === key ? (s.dir === 1 ? { key, dir: -1 } : null) : { key, dir: 1 }))\r\n }\r\n\r\n /**\r\n * Selecção actual da coluna aberta, já materializada.\r\n *\r\n * No modo Excel, sem entrada no mapa está tudo seleccionado — e é aqui que isso passa de\r\n * implícito a explícito, na primeira vez que se desmarca alguma coisa. Só serve a coluna\r\n * aberta: `openFilterOptions` é dela.\r\n */\r\n const selecaoDaColuna = (key: string) =>\r\n new Set(columnFilters[key] ?? (modoExcel ? openFilterOptions : []))\r\n\r\n const aplicarSelecao = (key: string, sel: Set<string>) =>\r\n setColumnFilters((prev) => {\r\n const proximo = { ...prev }\r\n // Tudo marcado é o mesmo que não haver filtro. Apagar a chave desliga o funil e tira o\r\n // chip, em vez de deixar um filtro que não filtra nada.\r\n if (modoExcel && sel.size === openFilterOptions.length) delete proximo[key]\r\n else proximo[key] = sel\r\n return proximo\r\n })\r\n\r\n const toggleFilterValue = (key: string, val: string) => {\r\n if (!allData) onDataNeeded?.()\r\n const sel = selecaoDaColuna(key)\r\n if (sel.has(val)) sel.delete(val)\r\n else sel.add(val)\r\n aplicarSelecao(key, sel)\r\n }\r\n\r\n /** `(Selecionar tudo)` — trabalha sobre os `correspondentes`, não sobre o que está desenhado. */\r\n const alternarTodos = (key: string, marcar: boolean) => {\r\n if (!allData) onDataNeeded?.()\r\n const sel = selecaoDaColuna(key)\r\n for (const v of opcoesVisiveis.correspondentes) {\r\n if (marcar) sel.add(v)\r\n else sel.delete(v)\r\n }\r\n aplicarSelecao(key, sel)\r\n }\r\n\r\n const setRange = (key: string, campo: 'min' | 'max', valor: string, tipo: DataTableColumn<T>['sortType']) => {\r\n if (!allData) onDataNeeded?.()\r\n setRangeFilters((prev) => {\r\n const atual = { ...(prev[key] ?? {}) }\r\n // Datas chegam do `<input type=\"date\">` em 'aaaa-mm-dd'; guarda-se o timestamp para o\r\n // filtro comparar sempre números. O `max` de uma data cobre o dia todo.\r\n const n = tipo === 'date' ? timestampDeData(valor) + (campo === 'max' ? 86_399_999 : 0) : Number(valor)\r\n if (valor === '' || !Number.isFinite(n)) delete atual[campo]\r\n else atual[campo] = n\r\n const next = { ...prev }\r\n if (atual.min == null && atual.max == null) delete next[key]\r\n else next[key] = atual\r\n return next\r\n })\r\n }\r\n\r\n const clearColumnFilter = (key: string) => {\r\n setColumnFilters((prev) => {\r\n const n = { ...prev }\r\n delete n[key]\r\n return n\r\n })\r\n setRangeFilters((prev) => {\r\n const n = { ...prev }\r\n delete n[key]\r\n return n\r\n })\r\n }\r\n\r\n const clearAllFilters = () => {\r\n setColumnFilters({})\r\n setRangeFilters({})\r\n }\r\n\r\n const temIntervalo = (key: string) => {\r\n const r = rangeFilters[key]\r\n return !!r && (r.min != null || r.max != null)\r\n }\r\n // No modo Excel o funil acende com a chave presente, mesmo com o conjunto vazio.\r\n const temFiltro = (key: string) =>\r\n (modoExcel ? columnFilters[key] !== undefined : (columnFilters[key]?.size ?? 0) > 0) || temIntervalo(key)\r\n\r\n const handleExport = () => {\r\n if (onExport) return onExport(sorted)\r\n const csv = toCsv(columns, sorted)\r\n const blob = new Blob([`${csv}`], { type: 'text/csv;charset=utf-8;' })\r\n const url = URL.createObjectURL(blob)\r\n const a = document.createElement('a')\r\n a.href = url\r\n a.download = `${exportFileName}.csv`\r\n a.click()\r\n URL.revokeObjectURL(url)\r\n }\r\n\r\n const alignClass = (a?: DataTableColumn<T>['align']) =>\r\n a === 'right' ? 'text-right' : a === 'center' ? 'text-center' : 'text-left'\r\n\r\n /** `sticky: true` mantem o significado antigo: presa a esquerda. */\r\n const ePresaAEsquerda = (c: DataTableColumn<T>) => c.sticky === true || c.sticky === 'left'\r\n\r\n // Offset acumulado por coluna sticky — sem isto, 2+ colunas sticky ficavam todas em\r\n // `left:0`, sobrepostas (a 2.ª por cima da 1.ª). Colunas sticky sem `width` usam um valor\r\n // por defeito razoável para a acumulação (não afeta a largura real da coluna).\r\n const stickyLeft = useMemo(() => {\r\n const offsets: Record<string, number> = {}\r\n let acc = selectable ? 40 : 0\r\n for (const c of columns) {\r\n if (!ePresaAEsquerda(c)) continue\r\n offsets[c.key] = acc\r\n acc += c.width ?? DEFAULT_STICKY_WIDTH\r\n }\r\n return offsets\r\n }, [columns, selectable])\r\n\r\n // À direita acumula-se ao contrário: a última coluna encosta a 0 e as\r\n // anteriores empilham-se por cima da largura das seguintes.\r\n const stickyRight = useMemo(() => {\r\n const offsets: Record<string, number> = {}\r\n let acc = 0\r\n for (let i = columns.length - 1; i >= 0; i--) {\r\n const c = columns[i]\r\n if (c.sticky !== 'right') continue\r\n offsets[c.key] = acc\r\n acc += c.width ?? DEFAULT_STICKY_WIDTH\r\n }\r\n return offsets\r\n }, [columns])\r\n\r\n const estiloPreso = (c: DataTableColumn<T>): React.CSSProperties | undefined => {\r\n if (ePresaAEsquerda(c)) return { left: stickyLeft[c.key] }\r\n if (c.sticky === 'right') return { right: stickyRight[c.key] }\r\n return undefined\r\n }\r\n\r\n const nColunas = columns.length + (selectable ? 1 : 0)\r\n\r\n // Enquanto o dataset completo não chega, filtrar/ordenar/agrupar só olharia para a página —\r\n // melhor esperar do que mostrar uma lista errada por baixo de um total nos milhares.\r\n const aEsperarDataset = !allData && !!isAllDataLoading && (hasActiveFilters || !!sort || isGrouped)\r\n\r\n // Antes dos returns de 'a carregar'/erro: um hook depois deles muda a ordem dos hooks entre renders.\r\n // Por omissão o cabeçalho oferece ordenar, filtrar e agrupar (ver `filterable`/`groupable`). Colunas sem dados\r\n // em nenhuma linha (ex.: botões de ações) não têm o que filtrar nem agrupar.\r\n const linhasBase = allData ?? data\r\n const semDados = useMemo(\r\n () => new Set(columns.filter((c) => !linhasBase.some((r) => rawValue(r, c) !== null)).map((c) => c.key)),\r\n [columns, linhasBase]\r\n )\r\n const filtravel = (c: DataTableColumn<T>) => c.filterable ?? !semDados.has(c.key)\r\n const agrupavel = (c: DataTableColumn<T>) =>\r\n c.groupable ?? (!semDados.has(c.key) && !c.groupAggregate && (!c.sortType || c.sortType === 'text'))\r\n\r\n if (isLoading) {\r\n return <div className=\"py-16 text-center text-sm font-bold uppercase tracking-widest text-slate-400\">{L.loading}</div>\r\n }\r\n if (isError) {\r\n return <div className=\"py-16 text-center text-sm font-bold uppercase tracking-widest text-red-400\">{L.error}</div>\r\n }\r\n\r\n // ─── Sub-render: popup de filtro ───────────────────────────────────────────\r\n\r\n const renderPopup = (key: string, col: DataTableColumn<T> | undefined, tituloExtra?: string) => {\r\n const { opts, total: totalOpcoes } = opcoesVisiveis\r\n const sel = columnFilters[key]\r\n const isFiltered = temFiltro(key)\r\n const podeOrdenar = !!sortFromFilter && !!col && col.sortable !== false\r\n const temRange = !!col && (col.sortType === 'number' || col.sortType === 'date')\r\n return createPortal(\r\n <div\r\n ref={popupRef}\r\n style={{ position: 'fixed', top: popupPos!.top, left: popupPos!.left }}\r\n className=\"z-[100] w-72 max-w-[80vw] bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 shadow-2xl rounded-xl overflow-hidden\"\r\n >\r\n {tituloExtra && (\r\n <div className=\"px-3 pt-2.5 pb-2 border-b border-slate-100 dark:border-slate-800\">\r\n <span className=\"text-[9px] font-black uppercase tracking-widest text-proef-blue dark:text-proef-turquoise\">{tituloExtra}</span>\r\n </div>\r\n )}\r\n {!allData && isAllDataLoading && (\r\n // As opções à vista ainda são só as de `data`. Dizê-lo é melhor do que deixar\r\n // escolher de uma lista incompleta.\r\n <div className=\"px-2 py-1.5 bg-amber-50 dark:bg-amber-500/10 border-b border-amber-100 dark:border-amber-500/20 text-[10px] text-amber-700 dark:text-amber-300\">\r\n {L.loadingOptions}\r\n </div>\r\n )}\r\n {podeOrdenar && (\r\n <div className=\"border-b border-slate-100 dark:border-slate-800 p-1.5 space-y-0.5\">\r\n <button\r\n onClick={() => {\r\n if (!allData) onDataNeeded?.()\r\n setSort({ key, dir: 1 })\r\n }}\r\n className={`appearance-none bg-transparent border-0 cursor-pointer w-full flex items-center gap-2 px-2.5 py-1.5 text-[11px] font-semibold text-left hover:bg-slate-50 dark:hover:bg-slate-800 ${\r\n sort?.key === key && sort.dir === 1 ? 'text-proef-blue dark:text-proef-turquoise bg-slate-50 dark:bg-slate-800' : 'text-slate-600 dark:text-slate-300'\r\n }`}\r\n >\r\n ▲ {L.sortAsc}\r\n </button>\r\n <button\r\n onClick={() => {\r\n if (!allData) onDataNeeded?.()\r\n setSort({ key, dir: -1 })\r\n }}\r\n className={`appearance-none bg-transparent border-0 cursor-pointer w-full flex items-center gap-2 px-2.5 py-1.5 text-[11px] font-semibold text-left hover:bg-slate-50 dark:hover:bg-slate-800 ${\r\n sort?.key === key && sort.dir === -1 ? 'text-proef-blue dark:text-proef-turquoise bg-slate-50 dark:bg-slate-800' : 'text-slate-600 dark:text-slate-300'\r\n }`}\r\n >\r\n ▼ {L.sortDesc}\r\n </button>\r\n </div>\r\n )}\r\n <div className=\"p-2\">\r\n {temRange && (\r\n // Intervalo antes da lista: numa coluna numérica ou de datas é o que se procura\r\n // (\"acima de 30 dias\", \"a partir de julho\"), não um valor exato numa lista de dezenas.\r\n <div className=\"flex items-center gap-1.5 mb-2\">\r\n <input\r\n type={col!.sortType === 'date' ? 'date' : 'number'}\r\n defaultValue=\"\"\r\n onChange={(e) => setRange(key, 'min', e.target.value, col!.sortType)}\r\n placeholder={L.min}\r\n title={L.min}\r\n className=\"w-1/2 bg-slate-50 dark:bg-slate-800 dark:text-slate-100 dark:[color-scheme:dark] border border-slate-200 dark:border-slate-700 rounded-lg px-2 py-1.5 text-xs outline-none focus:border-proef-blue dark:focus:border-proef-turquoise\"\r\n />\r\n <input\r\n type={col!.sortType === 'date' ? 'date' : 'number'}\r\n defaultValue=\"\"\r\n onChange={(e) => setRange(key, 'max', e.target.value, col!.sortType)}\r\n placeholder={L.max}\r\n title={L.max}\r\n className=\"w-1/2 bg-slate-50 dark:bg-slate-800 dark:text-slate-100 dark:[color-scheme:dark] border border-slate-200 dark:border-slate-700 rounded-lg px-2 py-1.5 text-xs outline-none focus:border-proef-blue dark:focus:border-proef-turquoise\"\r\n />\r\n </div>\r\n )}\r\n <input\r\n autoFocus\r\n value={filterSearch}\r\n onChange={(e) => setFilterSearch(e.target.value)}\r\n placeholder={L.search}\r\n className=\"w-full bg-slate-50 dark:bg-slate-800 dark:text-slate-100 border border-slate-200 dark:border-slate-700 rounded-lg px-2 py-1.5 text-xs outline-none focus:border-proef-blue dark:focus:border-proef-turquoise mb-2\"\r\n />\r\n {/* `overflow-x-hidden` explícito: com só `overflow-y-auto`, o CSS calcula\r\n `overflow-x: auto` e a barra vertical rouba largura ao conteúdo até aparecer uma\r\n segunda barra, horizontal, que não faz falta nenhuma. */}\r\n <div className=\"max-h-64 overflow-y-auto overflow-x-hidden space-y-0.5\">\r\n {modoExcel && opts.length > 0 && (() => {\r\n // Tri-state sobre os valores que a pesquisa apanha: ✓ todos, – alguns, vazio nenhum.\r\n const marcados = opcoesVisiveis.correspondentes.filter((v) => (sel ? sel.has(v) : true)).length\r\n const todos = marcados === opcoesVisiveis.correspondentes.length\r\n const alguns = marcados > 0 && !todos\r\n return (\r\n <button\r\n onClick={() => alternarTodos(key, !todos)}\r\n className=\"appearance-none bg-transparent border-0 border-b border-slate-100 dark:border-slate-800 cursor-pointer w-full flex items-center gap-2 px-1.5 py-1 mb-0.5 hover:bg-slate-50 dark:hover:bg-slate-800 text-left normal-case tracking-normal\"\r\n >\r\n <span\r\n className={`w-3.5 h-3.5 flex items-center justify-center border shrink-0 rounded ${\r\n todos || alguns ? 'bg-proef-navy dark:bg-proef-turquoise border-proef-navy dark:border-proef-turquoise text-white dark:text-proef-navy' : 'border-slate-300 dark:border-slate-600'\r\n }`}\r\n >\r\n {todos && <span className=\"text-[9px] leading-none\">✓</span>}\r\n {alguns && <span className=\"text-[9px] leading-none\">–</span>}\r\n </span>\r\n <span className=\"text-xs text-slate-700 dark:text-slate-200 font-semibold break-words\">\r\n {filterSearch ? L.selectAllResults : L.selectAll}\r\n </span>\r\n </button>\r\n )\r\n })()}\r\n {opts.length === 0 ? (\r\n <p className=\"text-[11px] text-slate-400 text-center py-3\">{L.noOptions}</p>\r\n ) : (\r\n opts.map((v) => {\r\n // Sem entrada no mapa está tudo marcado, daí o `sel ? … : modoExcel`.\r\n const checked = sel ? sel.has(v) : modoExcel\r\n return (\r\n <button\r\n key={v}\r\n onClick={() => toggleFilterValue(key, v)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer w-full flex items-center gap-2 px-1.5 py-1 hover:bg-slate-50 dark:hover:bg-slate-800 text-left normal-case tracking-normal\"\r\n >\r\n <span\r\n className={`w-3.5 h-3.5 flex items-center justify-center border shrink-0 rounded ${\r\n checked ? 'bg-proef-navy dark:bg-proef-turquoise border-proef-navy dark:border-proef-turquoise text-white dark:text-proef-navy' : 'border-slate-300 dark:border-slate-600'\r\n }`}\r\n >\r\n {checked && <span className=\"text-[9px] leading-none\">✓</span>}\r\n </span>\r\n <span className=\"text-xs text-slate-700 dark:text-slate-200 font-normal break-words\">{v}</span>\r\n </button>\r\n )\r\n })\r\n )}\r\n </div>\r\n {totalOpcoes > opts.length && (\r\n <p className=\"text-[10.5px] text-slate-400 text-center pt-2\">\r\n {L.moreOptions.replace('{shown}', String(opts.length)).replace('{total}', String(totalOpcoes))}\r\n </p>\r\n )}\r\n {isFiltered && (\r\n <button\r\n onClick={() => clearColumnFilter(key)}\r\n className=\"appearance-none bg-transparent border-0 border-t border-slate-100 dark:border-slate-800 cursor-pointer w-full mt-2 pt-2 text-[10px] font-black uppercase tracking-wider text-red-400 hover:text-red-500\"\r\n >\r\n {L.clear}\r\n </button>\r\n )}\r\n </div>\r\n </div>,\r\n document.body\r\n )\r\n }\r\n\r\n const botaoFunil = (key: string, col: DataTableColumn<T> | undefined, extra?: { label: string }) => {\r\n const isOpen = openFilter === key\r\n const isFiltered = temFiltro(key)\r\n return (\r\n <div\r\n key={key}\r\n className=\"relative\"\r\n ref={(el) => {\r\n if (el) filterRefs.current.set(key, el)\r\n else filterRefs.current.delete(key)\r\n }}\r\n >\r\n <button\r\n onClick={() => {\r\n // Abrir o funil é a 1.ª vez que as opções de todas as páginas fazem falta — é aqui\r\n // que se pede o dataset completo.\r\n if (!isOpen && !allData) onDataNeeded?.()\r\n setOpenFilter(isOpen ? null : key)\r\n setFilterSearch('')\r\n }}\r\n className={`appearance-none border-0 bg-transparent cursor-pointer p-0.5 transition-colors ${\r\n extra\r\n ? isFiltered || isOpen\r\n ? 'text-violet-600 dark:text-violet-300'\r\n : 'text-slate-300 dark:text-slate-600 hover:text-slate-500 dark:hover:text-slate-300'\r\n : isFiltered || isOpen\r\n ? 'text-proef-turquoise'\r\n : 'text-slate-300 dark:text-slate-600 hover:text-slate-500 dark:hover:text-slate-300'\r\n }`}\r\n // Sem isto o funil e um botao sem nome acessivel: nem leitor de ecra nem teste lhe\r\n // chegam pelo nome da coluna.\r\n title={extra?.label ?? col?.label}\r\n >\r\n <Funnel size={extra ? 10 : 11} className={isFiltered ? 'fill-current' : ''} />\r\n </button>\r\n {isOpen && popupPos && renderPopup(key, col, extra?.label)}\r\n </div>\r\n )\r\n }\r\n\r\n // ─── Sub-render: linhas ────────────────────────────────────────────────────\r\n\r\n const renderLinha = (row: T, i: number) => {\r\n const k = chaveDe(row, i)\r\n const marcada = selectable && selectedKeys.has(k)\r\n return (\r\n <tr\r\n key={k}\r\n onClick={() => onRowClick?.(row)}\r\n onDoubleClick={() => onRowDoubleClick?.(row)}\r\n className={`group even:bg-slate-50/50 dark:even:bg-slate-800/40 hover:bg-slate-100/70 dark:hover:bg-slate-800/70 transition-colors ${\r\n onRowClick || onRowDoubleClick ? 'cursor-pointer' : ''\r\n } ${marcada ? 'bg-proef-turquoise/5' : ''} ${rowClassName?.(row) ?? ''}`}\r\n >\r\n {selectable && (\r\n <td\r\n className=\"px-3 py-2.5 border-b border-slate-100 dark:border-slate-800 w-10\"\r\n onClick={(e) => {\r\n e.stopPropagation()\r\n alternarLinha(k)\r\n }}\r\n >\r\n <span\r\n className={`w-4 h-4 flex items-center justify-center border transition-colors rounded ${\r\n marcada ? 'bg-proef-navy dark:bg-proef-turquoise border-proef-navy dark:border-proef-turquoise text-white dark:text-proef-navy' : 'border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500'\r\n }`}\r\n >\r\n {marcada && <span className=\"text-[9px] leading-none\">✓</span>}\r\n </span>\r\n </td>\r\n )}\r\n {columns.map((c) => (\r\n <td\r\n key={c.key}\r\n style={estiloPreso(c)}\r\n // Fundo sólido explícito (não `bg-inherit`) — uma coluna sticky com fundo\r\n // transparente/translúcido deixa o conteúdo que passa por baixo, ao fazer\r\n // scroll horizontal, transparecer por trás do texto (sobreposição ilegível).\r\n // `group-hover` porque `:hover` isolado na célula não reage ao hover na linha.\r\n className={`px-3 py-2.5 border-b border-slate-100 dark:border-slate-800 whitespace-nowrap ${alignClass(c.align)} ${\r\n c.sticky ? `sticky z-[1] ${c.sticky === 'right' ? 'border-l border-slate-200 dark:border-slate-700' : ''} ${i % 2 === 1 ? 'bg-slate-50 dark:bg-slate-800' : 'bg-white dark:bg-slate-900'} group-hover:bg-slate-100 dark:group-hover:bg-slate-800` : ''\r\n } ${c.cellClassName ?? ''}`}\r\n >\r\n {(() => {\r\n const conteudo = c.render ? c.render(row) : rawValue(row, c) ?? <span className=\"text-slate-400\">{EMPTY}</span>\r\n const max = larguraMax(c)\r\n if (!max) return conteudo\r\n const v = rawValue(row, c)\r\n return (\r\n <div className=\"overflow-hidden text-ellipsis\" style={{ maxWidth: max, width: larguras[c.key] }} title={v !== null && v !== undefined ? String(v) : undefined}>\r\n {conteudo}\r\n </div>\r\n )\r\n })()}\r\n </td>\r\n ))}\r\n </tr>\r\n )\r\n }\r\n\r\n // Colunas com valor nas linhas de grupo/total: o rótulo ocupa as colunas até à primeira com valor; daí em\r\n // diante cada coluna tem a sua célula, alinhada com as linhas.\r\n\r\n const primeiraComValor = columns.findIndex((c) => !!c.groupAggregate)\r\n const colunasComValor = primeiraComValor >= 0 ? columns.slice(primeiraComValor) : []\r\n const colunasRotuloGrupo = primeiraComValor >= 0 ? primeiraComValor + (selectable ? 1 : 0) : nColunas\r\n\r\n const renderGrupos = (nos: GrupoNo<T>[], nivel: number): React.ReactNode =>\r\n nos.map((no) => {\r\n const recolhido = collapsedGroups.has(no.path)\r\n return (\r\n <Fragment key={no.path}>\r\n <tr className=\"bg-slate-50/80 dark:bg-slate-800/60 cursor-pointer select-none\" onClick={() => alternarColapso(no.path)}>\r\n <td colSpan={colunasRotuloGrupo} className=\"px-3 py-1.5 border-b border-t border-slate-100 dark:border-slate-800\">\r\n <div className=\"flex items-center gap-1.5\" style={{ paddingLeft: nivel * 16 }}>\r\n <Caret\r\n size={11}\r\n className={`text-proef-blue/60 dark:text-proef-turquoise/70 transition-transform shrink-0 ${recolhido ? '' : 'rotate-90'}`}\r\n />\r\n <span className=\"text-[10px] font-black uppercase tracking-[0.15em] text-proef-blue/70 dark:text-proef-turquoise/80\">{no.val}</span>\r\n <span className=\"text-[10px] text-slate-400 ml-1\">({no.items.length})</span>\r\n </div>\r\n </td>\r\n {colunasComValor.map((c) => (\r\n <td key={c.key} className={`px-3 py-1.5 border-b border-t border-slate-100 dark:border-slate-800 whitespace-nowrap text-xs font-bold text-slate-700 dark:text-slate-200 ${alignClass(c.align)}`}>\r\n {c.groupAggregate?.(no.items)}\r\n </td>\r\n ))}\r\n </tr>\r\n {!recolhido &&\r\n (no.filhos.length > 0\r\n ? renderGrupos(no.filhos, nivel + 1)\r\n : no.items.map((row) => renderLinha(row, indicePorLinha.get(row) ?? 0)))}\r\n </Fragment>\r\n )\r\n })\r\n\r\n // ─── Render ────────────────────────────────────────────────────────────────\r\n\r\n return (\r\n <div>\r\n <div className=\"flex items-center gap-2 mb-2.5\">\r\n <span className=\"text-xs text-slate-500 dark:text-slate-400\">{L.results.replace('{count}', String(sorted.length))}</span>\r\n <div className=\"ml-auto flex items-center gap-2\">\r\n {extraToolbar}\r\n {exportFileName && sorted.length > 0 && (\r\n <button\r\n onClick={handleExport}\r\n className=\"appearance-none cursor-pointer inline-flex items-center gap-1.5 h-8 px-3.5 rounded-lg border border-proef-navy dark:border-proef-turquoise bg-proef-navy dark:bg-proef-turquoise text-white dark:text-proef-navy hover:bg-proef-blue dark:hover:bg-proef-turquoise/80 text-[11px] font-black uppercase tracking-widest transition-colors\"\r\n >\r\n ⭳ {L.export}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Chips do agrupamento. */}\r\n {isGrouped && (\r\n <div className=\"mb-2 flex items-center gap-2 flex-wrap\">\r\n {groupByKeys.map((key) => {\r\n const col = columns.find((c) => c.key === key)\r\n return (\r\n <div\r\n key={key}\r\n className=\"flex items-center gap-1.5 bg-violet-100 dark:bg-violet-500/20 text-violet-800 dark:text-violet-200 px-2.5 py-1 text-[10px] font-black uppercase tracking-wider\"\r\n >\r\n <Layers size={9} />\r\n <span>{col?.label ?? key}</span>\r\n <button\r\n onClick={() => removerGrupo(key)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer ml-0.5 hover:text-violet-600\"\r\n >\r\n ✕\r\n </button>\r\n </div>\r\n )\r\n })}\r\n <button\r\n onClick={limparGrupos}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer text-[10px] font-black uppercase tracking-wider text-slate-400 hover:text-red-500 transition-colors\"\r\n >\r\n {L.clearGroups}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* Chips dos filtros ativos. */}\r\n {(activeFilters.length > 0 || activeRanges.length > 0) && (\r\n <div className=\"mb-2 flex items-center gap-2 flex-wrap\">\r\n {activeRanges.map(([key, r]) => {\r\n const col = columns.find((c) => c.key === key)\r\n const fmt = (v?: number) =>\r\n v == null ? '' : col?.sortType === 'date' ? new Date(v).toLocaleDateString() : String(v)\r\n const txt =\r\n r.min != null && r.max != null\r\n ? `${fmt(r.min)} – ${fmt(r.max)}`\r\n : r.min != null\r\n ? `≥ ${fmt(r.min)}`\r\n : `≤ ${fmt(r.max)}`\r\n return (\r\n <div\r\n key={`r-${key}`}\r\n className=\"flex items-center gap-1.5 bg-proef-turquoise/15 text-proef-navy dark:text-proef-turquoise px-2.5 py-1 text-[10px] font-black uppercase tracking-wider\"\r\n >\r\n <span>\r\n {col?.label ?? key}: {txt}\r\n </span>\r\n <button\r\n onClick={() => clearColumnFilter(key)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer ml-0.5 hover:text-proef-blue dark:hover:text-proef-turquoise\"\r\n >\r\n ✕\r\n </button>\r\n </div>\r\n )\r\n })}\r\n {activeFilters.flatMap(([key, sel]) => {\r\n const col = columns.find((c) => c.key === key)\r\n const extra = !col ? columns.flatMap((c) => c.extraFilters ?? []).find((f) => f.key === key) : undefined\r\n const chipLabel = col?.label ?? extra?.label ?? key\r\n const arr = Array.from(sel)\r\n\r\n // Coluna com cores por valor: um chip colorido por valor, removível à parte.\r\n if (col?.valueChipColors) {\r\n const cores = col.valueChipColors\r\n return arr.map((v) => (\r\n <div\r\n key={`${key}:${v}`}\r\n className={`flex items-center gap-1.5 px-2.5 py-1 text-[10px] font-black uppercase tracking-wider ${\r\n cores[v] ?? 'bg-proef-turquoise/15 text-proef-navy dark:text-proef-turquoise'\r\n }`}\r\n >\r\n <span>\r\n {chipLabel}: {v}\r\n </span>\r\n <button\r\n onClick={() => toggleFilterValue(key, v)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer ml-0.5 hover:opacity-70\"\r\n >\r\n ✕\r\n </button>\r\n </div>\r\n ))\r\n }\r\n\r\n // Com 0 marcados o chip tem de dizer \"0\" — juntar uma lista vazia dava um chip mudo.\r\n const txt =\r\n arr.length > 2 || arr.length === 0\r\n ? L.selected.replace('{count}', String(arr.length))\r\n : arr.join(', ')\r\n return [\r\n <div\r\n key={key}\r\n className=\"flex items-center gap-1.5 bg-proef-turquoise/15 text-proef-navy dark:text-proef-turquoise px-2.5 py-1 text-[10px] font-black uppercase tracking-wider\"\r\n >\r\n <span>\r\n {chipLabel}: {txt}\r\n </span>\r\n <button\r\n onClick={() => clearColumnFilter(key)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer ml-0.5 hover:text-proef-blue dark:hover:text-proef-turquoise\"\r\n >\r\n ✕\r\n </button>\r\n </div>,\r\n ]\r\n })}\r\n <button\r\n onClick={clearAllFilters}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer text-[10px] font-black uppercase tracking-wider text-slate-400 hover:text-red-500 transition-colors\"\r\n >\r\n {L.clearAll}\r\n </button>\r\n </div>\r\n )}\r\n\r\n {/* rounded-xl: os mesmos cantos dos cartões da biblioteca (Ui.tsx) e das tabelas dos portais. */}\r\n <div className=\"overflow-auto rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900\" style={{ maxHeight }}>\r\n <table className=\"w-full text-[13px] border-collapse dark:text-slate-200\">\r\n <thead className=\"sticky top-0 z-[2]\">\r\n <tr>\r\n {selectable && (\r\n <th className=\"bg-slate-50 dark:bg-slate-800 px-3 py-2.5 border-b border-slate-200 dark:border-slate-700 w-10 sticky left-0 z-[3]\">\r\n <button\r\n onClick={alternarTodas}\r\n className={`appearance-none cursor-pointer w-4 h-4 flex items-center justify-center border transition-colors rounded ${\r\n todasVisiveisMarcadas\r\n ? 'bg-proef-navy dark:bg-proef-turquoise border-proef-navy dark:border-proef-turquoise text-white dark:text-proef-navy'\r\n : algumaVisivelMarcada\r\n ? 'bg-slate-300 dark:bg-slate-600 border-slate-300 dark:border-slate-600 text-white'\r\n : 'bg-white dark:bg-slate-900 border-slate-300 dark:border-slate-600 hover:border-slate-400 dark:hover:border-slate-500'\r\n }`}\r\n >\r\n {(todasVisiveisMarcadas || algumaVisivelMarcada) && <span className=\"text-[9px] leading-none\">✓</span>}\r\n </button>\r\n </th>\r\n )}\r\n {columns.map((c) => {\r\n const isFiltered = temFiltro(c.key)\r\n const podeOrdenar = c.sortable !== false\r\n const podeAgrupar = agrupavel(c)\r\n const agrupada = groupByKeys.includes(c.key)\r\n return (\r\n <th\r\n key={c.key}\r\n style={{\r\n ...(c.width ? { minWidth: c.width } : {}),\r\n ...(larguras[c.key] ? { width: larguras[c.key], minWidth: larguras[c.key], maxWidth: larguras[c.key] } : {}),\r\n ...(estiloPreso(c) ?? {}),\r\n }}\r\n className={`group/th relative bg-slate-50 dark:bg-slate-800 text-[11.5px] uppercase tracking-wide text-slate-500 dark:text-slate-400 font-semibold px-3 py-2.5 border-b border-slate-200 dark:border-slate-700 whitespace-nowrap select-none ${alignClass(\r\n c.align\r\n )} ${c.sticky ? `sticky z-[3] bg-slate-50 dark:bg-slate-800 ${c.sticky === 'right' ? 'border-l border-slate-200 dark:border-slate-700' : ''}` : ''} ${c.headerClassName ?? ''}`}\r\n >\r\n {resizableColumns && (\r\n // Pega no limite direito: arrastar redimensiona; duplo clique repõe a largura automática\r\n <span\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n title={L.resizeColumn}\r\n onMouseDown={(e) => iniciarResize(e, c.key)}\r\n onDoubleClick={() => reporLargura(c.key)}\r\n onClick={(e) => e.stopPropagation()}\r\n className=\"absolute right-0 top-0 z-[4] h-full w-1.5 cursor-col-resize bg-transparent hover:bg-proef-blue/40 group-hover/th:bg-slate-300/60 dark:group-hover/th:bg-slate-600/60\"\r\n />\r\n )}\r\n <div className=\"flex items-center gap-1.5\">\r\n {podeOrdenar ? (\r\n <button\r\n onClick={() => toggleSort(c.key)}\r\n className=\"appearance-none bg-transparent border-0 cursor-pointer p-0 uppercase tracking-wide text-slate-500 dark:text-slate-400 hover:text-proef-blue dark:hover:text-proef-turquoise font-semibold\"\r\n >\r\n {c.label}\r\n {sort?.key === c.key && <span className=\"ml-1\">{sort.dir === 1 ? '▲' : '▼'}</span>}\r\n </button>\r\n ) : (\r\n <span>{c.label}</span>\r\n )}\r\n\r\n {podeAgrupar && (\r\n <button\r\n onClick={() => alternarGrupo(c.key)}\r\n title={(agrupada ? L.ungroup : L.groupBy).replace('{label}', c.label)}\r\n className={`appearance-none bg-transparent border-0 cursor-pointer p-0.5 transition-colors ${\r\n agrupada ? 'text-violet-600 dark:text-violet-300' : 'text-slate-300 dark:text-slate-600 hover:text-slate-500 dark:hover:text-slate-300'\r\n }`}\r\n >\r\n <Layers size={10} />\r\n </button>\r\n )}\r\n\r\n {filtravel(c) && botaoFunil(c.key, c)}\r\n\r\n {c.extraFilters?.map((ef) => botaoFunil(ef.key, undefined, { label: ef.label }))}\r\n </div>\r\n {c.headerExtra && (\r\n <div className=\"mt-0.5 normal-case font-normal tracking-normal\">{c.headerExtra(filtered)}</div>\r\n )}\r\n </th>\r\n )\r\n })}\r\n </tr>\r\n </thead>\r\n <tbody>\r\n {aEsperarDataset ? (\r\n <tr>\r\n <td colSpan={nColunas} className=\"py-16 text-center text-sm font-bold uppercase tracking-widest text-slate-400\">\r\n {L.loading}\r\n </td>\r\n </tr>\r\n ) : isGrouped ? (\r\n grupos.length === 0 ? (\r\n <tr>\r\n <td colSpan={nColunas} className=\"py-16 text-center text-sm text-slate-400\">\r\n {L.empty}\r\n </td>\r\n </tr>\r\n ) : (\r\n renderGrupos(grupos, 0)\r\n )\r\n ) : sorted.length === 0 ? (\r\n <tr>\r\n <td colSpan={nColunas} className=\"py-16 text-center\">\r\n {emptyIcon && <div className=\"flex justify-center mb-2\">{emptyIcon}</div>}\r\n <p className=\"text-sm text-slate-400\">{L.empty}</p>\r\n {emptySubLabel && <p className=\"text-xs text-slate-400 mt-1\">{emptySubLabel}</p>}\r\n </td>\r\n </tr>\r\n ) : (\r\n pageData.map((row, i) => renderLinha(row, i))\r\n )}\r\n </tbody>\r\n {totalRow && !aEsperarDataset && sorted.length > 0 && (\r\n <tfoot className=\"sticky bottom-0 z-[2]\">\r\n <tr className=\"bg-slate-100 dark:bg-slate-800 font-black text-slate-800 dark:text-slate-100\">\r\n <td colSpan={colunasRotuloGrupo} className=\"px-3 py-2 border-t-2 border-slate-200 dark:border-slate-700 text-[10px] uppercase tracking-widest\">\r\n {L.total}\r\n </td>\r\n {colunasComValor.map((c) => (\r\n <td key={c.key} className={`px-3 py-2 border-t-2 border-slate-200 dark:border-slate-700 whitespace-nowrap text-xs ${alignClass(c.align)}`}>\r\n {c.groupAggregate?.(sorted)}\r\n </td>\r\n ))}\r\n </tr>\r\n </tfoot>\r\n )}\r\n </table>\r\n </div>\r\n\r\n {paginando && (totalPages > 1 || (pageSizeOptions?.length ?? 0) > 0) && (\r\n <div className=\"mt-2.5 flex items-center justify-between gap-2\">\r\n <span className=\"text-[10px] font-black uppercase tracking-widest text-slate-400\">\r\n {L.page.replace('{page}', String(currentPage)).replace('{pages}', String(totalPages))}\r\n </span>\r\n <div className=\"flex items-center gap-1\">\r\n {(pageSizeOptions?.length ?? 0) > 0 && (\r\n <span className=\"flex items-center gap-1.5 mr-2 text-[10px] font-black uppercase tracking-widest text-slate-400\">\r\n {L.perPage}\r\n {/* Mesma lista suspensa dos portais (Escolha), não o <select> nativo. */}\r\n <Escolha\r\n rotulo={L.perPage}\r\n valor={String(pageSizeAtual)}\r\n onMudar={(v) => setPageSizeAtual(Number(v))}\r\n className=\"w-20 normal-case tracking-normal font-normal\"\r\n opcoes={[...new Set([...(pageSizeOptions ?? []), pageSizeAtual!])].sort((a, b) => a - b).map(String)}\r\n />\r\n </span>\r\n )}\r\n <button\r\n disabled={currentPage <= 1}\r\n onClick={() => setPage((p) => Math.max(1, p - 1))}\r\n className=\"appearance-none cursor-pointer bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-[10px] font-black uppercase rounded-lg tracking-widest px-3 py-1.5 text-slate-500 dark:text-slate-400 hover:enabled:border-proef-navy dark:hover:enabled:border-proef-turquoise hover:enabled:text-proef-navy dark:hover:enabled:text-proef-turquoise disabled:opacity-30 disabled:cursor-not-allowed transition-colors\"\r\n >\r\n {L.prev}\r\n </button>\r\n <button\r\n disabled={currentPage >= totalPages}\r\n onClick={() => setPage((p) => Math.min(totalPages, p + 1))}\r\n className=\"appearance-none cursor-pointer bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 text-[10px] font-black uppercase rounded-lg tracking-widest px-3 py-1.5 text-slate-500 dark:text-slate-400 hover:enabled:border-proef-navy dark:hover:enabled:border-proef-turquoise hover:enabled:text-proef-navy dark:hover:enabled:text-proef-turquoise disabled:opacity-30 disabled:cursor-not-allowed transition-colors\"\r\n >\r\n {L.next}\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"names":["v"],"mappings":"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"}
|
|
@@ -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\n\r\nconst DialogContainerContext = createContext<HTMLElement | null>(null)\r\n\r\n/**\r\n * O elemento da janela aberta, para usar como destino de `createPortal` (ex:\r\n * avisos, confirmações, sobreposições). Com a janela aberta, tudo o que está\r\n * fora dela fica por baixo e não responde; um portal para `document.body` fica\r\n * escondido atrás. 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 title: string\r\n /** Linha por baixo do título (ex: nome · departamento). */\r\n subtitle?: string\r\n /** Botões no rodapé, sempre visíveis (ex: Confirmar, Fechar). */\r\n footer?: React.ReactNode\r\n /** Largura máxima: `sm` 480px, `md` 720px, `lg` 1000px. Nunca passa o ecrã. */\r\n size?: 'sm' | 'md' | 'lg'\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' }\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.\r\n *\r\n * É a janela modal da V1 (substitui o `Modal` legacy).\r\n *\r\n * Usa o [<dialog>](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog)\r\n * do browser: fundo escurecido, Esc para fechar, o teclado fica dentro da\r\n * janela e, ao fechar, volta ao sítio onde estava.\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\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 ref = useRef<HTMLDialogElement>(null)\r\n const [container, setContainer] = useState<HTMLDialogElement | null>(null)\r\n const titleId = useId()\r\n\r\n useEffect(() => {\r\n setContainer(ref.current)\r\n }, [])\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 const dialog = ref.current\r\n if (!dialog) return\r\n if (open && !dialog.open) dialog.showModal()\r\n if (!open && dialog.open) dialog.close()\r\n }, [open])\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 return (\r\n <dialog\r\n ref={ref}\r\n aria-labelledby={titleId}\r\n // Esc: o browser fecharia sozinho; quem decide é a app.\r\n onCancel={(e) => {\r\n e.preventDefault()\r\n onClose()\r\n }}\r\n onPointerDown={(e) => {\r\n pressedBackdrop.current = e.target === e.currentTarget\r\n }}\r\n onClick={(e) => {\r\n if (closeOnBackdrop && pressedBackdrop.current && e.target === e.currentTarget) onClose()\r\n }}\r\n className={\r\n `m-auto w-[calc(100vw-2*var(--spacing-dialog-margin))] ${WIDTHS[size]} ` +\r\n 'max-h-[calc(100dvh-2*var(--spacing-dialog-margin))] overflow-hidden rounded-2xl border-0 bg-card p-0 text-foreground shadow-modal ' +\r\n `open:flex open:flex-col ${height === 'full' ? 'h-[calc(100dvh-2*var(--spacing-dialog-margin))]' : ''} ` +\r\n 'backdrop:bg-black/40 motion-safe:open:animate-dialog-in motion-safe:backdrop:animate-backdrop-in'\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={container}>\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 </DialogContainerContext.Provider>\r\n </dialog>\r\n )\r\n}\r\n"],"names":[],"mappings":";;AAEA,MAAM,yBAAyB,cAAkC,IAAI;AAU9D,MAAM,qBAAqB,MAAM,WAAW,sBAAsB;AAgCzE,MAAM,SAAS,EAAE,IAAI,mBAAmB,IAAI,mBAAmB,IAAI,kBAAA;AAiBnE,SAAwB,OAAO;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,QAAQ;AAAA,EACR;AACF,GAAgB;AACd,QAAM,MAAM,OAA0B,IAAI;AAC1C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAmC,IAAI;AACzE,QAAM,UAAU,MAAA;AAEhB,YAAU,MAAM;AACd,iBAAa,IAAI,OAAO;AAAA,EAC1B,GAAG,CAAA,CAAE;AAGL,QAAM,kBAAkB,OAAO,KAAK;AAEpC,YAAU,MAAM;AACd,UAAM,SAAS,IAAI;AACnB,QAAI,CAAC,OAAQ;AACb,QAAI,QAAQ,CAAC,OAAO,aAAa,UAAA;AACjC,QAAI,CAAC,QAAQ,OAAO,aAAa,MAAA;AAAA,EACnC,GAAG,CAAC,IAAI,CAAC;AAGT,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,SAAS;AACtB,UAAM,SAAS,KAAK,MAAM;AAC1B,SAAK,MAAM,WAAW;AACtB,WAAO,MAAM;AACX,WAAK,MAAM,WAAW;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,mBAAiB;AAAA,MAEjB,UAAU,CAAC,MAAM;AACf,UAAE,eAAA;AACF,gBAAA;AAAA,MACF;AAAA,MACA,eAAe,CAAC,MAAM;AACpB,wBAAgB,UAAU,EAAE,WAAW,EAAE;AAAA,MAC3C;AAAA,MACA,SAAS,CAAC,MAAM;AACd,YAAI,mBAAmB,gBAAgB,WAAW,EAAE,WAAW,EAAE,cAAe,SAAA;AAAA,MAClF;AAAA,MACA,WACE,yDAAyD,OAAO,IAAI,CAAC,8JAE1C,WAAW,SAAS,oDAAoD,EAAE;AAAA,MAIvG,UAAA;AAAA,QAAA,qBAAC,UAAA,EAAO,WAAU,kHAChB,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,WACb,UAAA;AAAA,YAAA,oBAAC,MAAA,EAAG,IAAI,SAAS,WAAU,6DACxB,UAAA,OACH;AAAA,YACC,YAAY,oBAAC,KAAA,EAAE,WAAU,+DAA+D,UAAA,SAAA,CAAS;AAAA,UAAA,GACpG;AAAA,UACA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,cAAY;AAAA,cACZ,OAAO;AAAA,cACP,SAAS;AAAA,cACT,WAAU;AAAA,cACX,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QAED,GACF;AAAA,QAEA,qBAAC,uBAAuB,UAAvB,EAAgC,OAAO,WACtC,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAW,QAAQ,2DAA2D,sDAAuD,UAAS;AAAA,UAElJ,UAAU,oBAAC,UAAA,EAAO,WAAU,gGAAgG,UAAA,OAAA,CAAO;AAAA,QAAA,EAAA,CACtI;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
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\n\r\nconst DialogContainerContext = createContext<HTMLElement | null>(null)\r\n\r\n/**\r\n * O elemento da janela aberta, para usar como destino de `createPortal` (ex:\r\n * avisos, confirmações, sobreposições). Com a janela aberta, tudo o que está\r\n * fora dela fica por baixo e não responde; um portal para `document.body` fica\r\n * escondido atrás. 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 title: string\r\n /** Linha por baixo do título (ex: nome · departamento). */\r\n subtitle?: string\r\n /** Botões no rodapé, sempre visíveis (ex: Confirmar, Fechar). */\r\n footer?: React.ReactNode\r\n /** Largura máxima: `sm` 480px, `md` 720px, `lg` 1000px. Nunca passa o ecrã. */\r\n size?: 'sm' | 'md' | 'lg'\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' }\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.\r\n *\r\n * É a janela modal da V1 (substitui o `Modal` legacy).\r\n *\r\n * Usa o [<dialog>](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog)\r\n * do browser: fundo escurecido, Esc para fechar, o teclado fica dentro da\r\n * janela e, ao fechar, volta ao sítio onde estava.\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\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 ref = useRef<HTMLDialogElement>(null)\r\n const [container, setContainer] = useState<HTMLDialogElement | null>(null)\r\n const titleId = useId()\r\n\r\n useEffect(() => {\r\n setContainer(ref.current)\r\n }, [])\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 const dialog = ref.current\r\n if (!dialog) return\r\n if (open && !dialog.open) dialog.showModal()\r\n if (!open && dialog.open) dialog.close()\r\n }, [open])\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 return (\r\n <dialog\r\n ref={ref}\r\n aria-labelledby={titleId}\r\n // Esc: o browser fecharia sozinho; quem decide é a app.\r\n onCancel={(e) => {\r\n if (e.target !== e.currentTarget) return\r\n e.preventDefault()\r\n onClose()\r\n }}\r\n onPointerDown={(e) => {\r\n pressedBackdrop.current = e.target === e.currentTarget\r\n }}\r\n onClick={(e) => {\r\n if (closeOnBackdrop && pressedBackdrop.current && e.target === e.currentTarget) onClose()\r\n }}\r\n className={\r\n `m-auto w-[calc(100vw-2*var(--spacing-dialog-margin))] ${WIDTHS[size]} ` +\r\n 'max-h-[calc(100dvh-2*var(--spacing-dialog-margin))] overflow-hidden rounded-2xl border-0 bg-card p-0 text-foreground shadow-modal ' +\r\n `open:flex open:flex-col ${height === 'full' ? 'h-[calc(100dvh-2*var(--spacing-dialog-margin))]' : ''} ` +\r\n 'backdrop:bg-black/40 motion-safe:open:animate-dialog-in motion-safe:backdrop:animate-backdrop-in'\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={container}>\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 </DialogContainerContext.Provider>\r\n </dialog>\r\n )\r\n}\r\n"],"names":[],"mappings":";;AAEA,MAAM,yBAAyB,cAAkC,IAAI;AAU9D,MAAM,qBAAqB,MAAM,WAAW,sBAAsB;AAgCzE,MAAM,SAAS,EAAE,IAAI,mBAAmB,IAAI,mBAAmB,IAAI,kBAAA;AAiBnE,SAAwB,OAAO;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,SAAS;AAAA,EACT,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,QAAQ;AAAA,EACR;AACF,GAAgB;AACd,QAAM,MAAM,OAA0B,IAAI;AAC1C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAmC,IAAI;AACzE,QAAM,UAAU,MAAA;AAEhB,YAAU,MAAM;AACd,iBAAa,IAAI,OAAO;AAAA,EAC1B,GAAG,CAAA,CAAE;AAGL,QAAM,kBAAkB,OAAO,KAAK;AAEpC,YAAU,MAAM;AACd,UAAM,SAAS,IAAI;AACnB,QAAI,CAAC,OAAQ;AACb,QAAI,QAAQ,CAAC,OAAO,aAAa,UAAA;AACjC,QAAI,CAAC,QAAQ,OAAO,aAAa,MAAA;AAAA,EACnC,GAAG,CAAC,IAAI,CAAC;AAGT,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,OAAO,SAAS;AACtB,UAAM,SAAS,KAAK,MAAM;AAC1B,SAAK,MAAM,WAAW;AACtB,WAAO,MAAM;AACX,WAAK,MAAM,WAAW;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,mBAAiB;AAAA,MAEjB,UAAU,CAAC,MAAM;AACf,YAAI,EAAE,WAAW,EAAE,cAAe;AAClC,UAAE,eAAA;AACF,gBAAA;AAAA,MACF;AAAA,MACA,eAAe,CAAC,MAAM;AACpB,wBAAgB,UAAU,EAAE,WAAW,EAAE;AAAA,MAC3C;AAAA,MACA,SAAS,CAAC,MAAM;AACd,YAAI,mBAAmB,gBAAgB,WAAW,EAAE,WAAW,EAAE,cAAe,SAAA;AAAA,MAClF;AAAA,MACA,WACE,yDAAyD,OAAO,IAAI,CAAC,8JAE1C,WAAW,SAAS,oDAAoD,EAAE;AAAA,MAIvG,UAAA;AAAA,QAAA,qBAAC,UAAA,EAAO,WAAU,kHAChB,UAAA;AAAA,UAAA,qBAAC,OAAA,EAAI,WAAU,WACb,UAAA;AAAA,YAAA,oBAAC,MAAA,EAAG,IAAI,SAAS,WAAU,6DACxB,UAAA,OACH;AAAA,YACC,YAAY,oBAAC,KAAA,EAAE,WAAU,+DAA+D,UAAA,SAAA,CAAS;AAAA,UAAA,GACpG;AAAA,UACA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,cAAY;AAAA,cACZ,OAAO;AAAA,cACP,SAAS;AAAA,cACT,WAAU;AAAA,cACX,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QAED,GACF;AAAA,QAEA,qBAAC,uBAAuB,UAAvB,EAAgC,OAAO,WACtC,UAAA;AAAA,UAAA,oBAAC,OAAA,EAAI,WAAW,QAAQ,2DAA2D,sDAAuD,UAAS;AAAA,UAElJ,UAAU,oBAAC,UAAA,EAAO,WAAU,gGAAgG,UAAA,OAAA,CAAO;AAAA,QAAA,EAAA,CACtI;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|
|
@@ -177,6 +177,11 @@ interface DataTableProps<T> {
|
|
|
177
177
|
* Vem depois das classes da tabela, por isso ganha em caso de conflito.
|
|
178
178
|
*/
|
|
179
179
|
rowClassName?: (row: T) => string | undefined;
|
|
180
|
+
/**
|
|
181
|
+
* Chamado quando as linhas à vista mudam (filtros do cabeçalho, ordenação ou dados), com todas as páginas.
|
|
182
|
+
* Serve para indicadores fora da tabela (ex.: cartões no topo da página) acompanharem os filtros.
|
|
183
|
+
*/
|
|
184
|
+
onVisibleRowsChange?: (rows: T[]) => void;
|
|
180
185
|
isLoading?: boolean;
|
|
181
186
|
isError?: boolean;
|
|
182
187
|
/** max-height do viewport com scroll (default 70vh). */
|
|
@@ -270,5 +275,5 @@ export declare function pesquisaCuringa(q: string): (v: string) => boolean;
|
|
|
270
275
|
* filtrar, ordenar e agrupar só olhariam para a página à vista.
|
|
271
276
|
* @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.
|
|
272
277
|
*/
|
|
273
|
-
export default function DataTable<T>({ columns, data, allData, onDataNeeded, isAllDataLoading, alwaysClient, onClientModeChange, rowKey, onRowClick, onRowDoubleClick, rowClassName, isLoading, isError, maxHeight, pageSize, pageSizeOptions, selectable, onSelectionChange, clearSelectionTrigger, storageKey, defaultMaxColumnWidth, resizableColumns, sortFromFilter, filterMode, defaultSortKey, defaultSortDir, viewRef, exportFileName, onExport, extraToolbar, emptySubLabel, emptyIcon, labels, totalRow, defaultGroupBy, }: DataTableProps<T>): React.JSX.Element;
|
|
278
|
+
export default function DataTable<T>({ columns, data, allData, onDataNeeded, isAllDataLoading, alwaysClient, onClientModeChange, rowKey, onRowClick, onRowDoubleClick, rowClassName, onVisibleRowsChange, isLoading, isError, maxHeight, pageSize, pageSizeOptions, selectable, onSelectionChange, clearSelectionTrigger, storageKey, defaultMaxColumnWidth, resizableColumns, sortFromFilter, filterMode, defaultSortKey, defaultSortDir, viewRef, exportFileName, onExport, extraToolbar, emptySubLabel, emptyIcon, labels, totalRow, defaultGroupBy, }: DataTableProps<T>): React.JSX.Element;
|
|
274
279
|
export {};
|