@praxisui/table 0.0.1
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/LICENSE +6 -0
- package/README.md +1084 -0
- package/fesm2022/praxisui-table-filter-form-dialog-host.component-DI8aWSSJ.mjs +85 -0
- package/fesm2022/praxisui-table-filter-form-dialog-host.component-DI8aWSSJ.mjs.map +1 -0
- package/fesm2022/praxisui-table.mjs +19350 -0
- package/fesm2022/praxisui-table.mjs.map +1 -0
- package/index.d.ts +1771 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,1084 @@
|
|
|
1
|
+
# @praxis/table
|
|
2
|
+
|
|
3
|
+
> Componente de tabela empresarial avançado com arquitetura unificada
|
|
4
|
+
|
|
5
|
+
## 🌟 Visão Geral
|
|
6
|
+
|
|
7
|
+
A biblioteca `@praxis/table` fornece um componente de tabela robusto e altamente configurável para aplicações Angular empresariais. Com a nova arquitetura unificada, oferece uma experiência de desenvolvimento simplificada mantendo todos os recursos avançados.
|
|
8
|
+
|
|
9
|
+
## ✨ Características Principais
|
|
10
|
+
|
|
11
|
+
### 🏗️ Arquitetura Unificada
|
|
12
|
+
|
|
13
|
+
- **Interface única**: `TableConfig` consolidada
|
|
14
|
+
- **Type Safety**: Tipagem forte em toda a API
|
|
15
|
+
- **Performance otimizada**: Eliminação de overhead de adaptação
|
|
16
|
+
- **API simplificada**: Menos confusão, mais produtividade
|
|
17
|
+
|
|
18
|
+
### 📊 Recursos Avançados
|
|
19
|
+
|
|
20
|
+
- **Paginação inteligente**: Client-side e server-side
|
|
21
|
+
- **Ordenação múltipla**: Suporte a multi-sort
|
|
22
|
+
- **Filtros dinâmicos**: Global e por coluna
|
|
23
|
+
- **Seleção de linhas**: Single, multiple e bulk actions
|
|
24
|
+
- **Redimensionamento**: Colunas redimensionáveis
|
|
25
|
+
- **Virtualização**: Para grandes volumes de dados
|
|
26
|
+
- **Exportação**: CSV, Excel, PDF
|
|
27
|
+
- **Acessibilidade**: WCAG 2.1 AA compliant
|
|
28
|
+
- **Verificação de Schema**: ETag/If-None-Match com notificações (somente em customização)
|
|
29
|
+
|
|
30
|
+
### 🎨 Editores Visuais
|
|
31
|
+
|
|
32
|
+
- **Behavior Editor**: Configuração de comportamentos
|
|
33
|
+
- **Columns Editor**: Gestão avançada de colunas
|
|
34
|
+
- **Toolbar Editor**: Personalização de ações
|
|
35
|
+
- **Messages Editor**: Textos e localização
|
|
36
|
+
|
|
37
|
+
### ⚙️ Painel de Configurações
|
|
38
|
+
|
|
39
|
+
Para abrir o painel de configurações, habilite o modo de edição na tabela:
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<praxis-table [editModeEnabled]="true"></praxis-table>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Um botão de engrenagem será exibido no canto superior direito. Ao clicar nele, o `SettingsPanel` é aberto permitindo ajustar:
|
|
46
|
+
|
|
47
|
+
- **Comportamento**: paginação, ordenação, filtros e recursos avançados.
|
|
48
|
+
- **Colunas**: visibilidade, ordem, largura e estilo.
|
|
49
|
+
- **Toolbar**: ações e botões da barra de ferramentas.
|
|
50
|
+
- **Mensagens**: textos e rótulos exibidos na interface.
|
|
51
|
+
|
|
52
|
+
As alterações podem ser aplicadas temporariamente com **Aplicar** ou salvas de forma persistente com **Salvar & Fechar**.
|
|
53
|
+
|
|
54
|
+
## 🚀 Instalação
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install @praxisui/core @praxisui/table
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Peers necessários (instale no app host):
|
|
61
|
+
- `@angular/core` `^20.0.0`, `@angular/common` `^20.0.0`
|
|
62
|
+
- `@praxisui/core` `^0.0.1`
|
|
63
|
+
- `@praxisui/dynamic-fields` `^0.0.1` (quando usar editores/inputs dinâmicos)
|
|
64
|
+
- `@praxisui/dynamic-form` `^0.0.1` (quando integrar com formulários dinâmicos)
|
|
65
|
+
- `@praxisui/settings-panel` `^0.0.1` (para painel de configuração embutido)
|
|
66
|
+
|
|
67
|
+
## 📝 Uso Básico
|
|
68
|
+
|
|
69
|
+
### Conectando ao Backend com `resourcePath`
|
|
70
|
+
|
|
71
|
+
A forma mais poderosa de usar a `<praxis-table>` é conectá-la diretamente a um endpoint de API compatível com o ecossistema Praxis. Isso é feito através do input `resourcePath`.
|
|
72
|
+
|
|
73
|
+
Quando `resourcePath` é fornecido, a tabela se torna "inteligente":
|
|
74
|
+
|
|
75
|
+
1. **Busca automática de dados**: A tabela gerencia a paginação, ordenação e filtros, fazendo as requisições necessárias ao backend.
|
|
76
|
+
2. **Geração dinâmica de colunas**: A tabela busca os metadados (schema) do backend para gerar as colunas automaticamente, respeitando as configurações definidas no `praxis-metadata-core` (via anotação `@UISchema`).
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<!-- Exemplo no template do seu componente -->
|
|
80
|
+
<praxis-table resourcePath="human-resources/departamentos" [editModeEnabled]="true"> </praxis-table>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Neste exemplo:
|
|
84
|
+
|
|
85
|
+
- `resourcePath="human-resources/departamentos"` instrui a tabela a se comunicar com o endpoint `/api/human-resources/departamentos`.
|
|
86
|
+
- A tabela fará requisições como `POST /api/human-resources/departamentos/filter` para obter os dados e `GET /api/human-resources/departamentos/schemas` para obter a configuração das colunas.
|
|
87
|
+
- `[editModeEnabled]="true"` permite a edição visual da configuração da tabela em tempo real.
|
|
88
|
+
|
|
89
|
+
### Empty State + Quick Connect (sem `resourcePath`)
|
|
90
|
+
|
|
91
|
+
Se a tabela for renderizada sem `resourcePath`, um cartão de "Empty State" é exibido convidando a conectar o componente a um recurso. Basta clicar em "Conectar a recurso" para abrir um painel rápido com um único campo:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
resourcePath: ex.: human-resources/departamentos
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Ao aplicar/salvar, a `<praxis-table>` é automaticamente configurada para buscar o schema e os dados do backend. Esse fluxo evita telas em branco e simplifica o onboarding do componente em páginas novas.
|
|
98
|
+
|
|
99
|
+
### Fluxo de Comunicação do `resourcePath`
|
|
100
|
+
|
|
101
|
+
O diagrama abaixo ilustra como a propriedade `resourcePath` conecta o componente frontend ao controller do backend. O fluxo de inicialização ocorre em três etapas principais: **Configurar**, **Carregar Schema** e **Buscar Dados**.
|
|
102
|
+
|
|
103
|
+
```mermaid
|
|
104
|
+
sequenceDiagram
|
|
105
|
+
participant FE_Component as Componente Angular<br>(departamentos.html)
|
|
106
|
+
participant Praxis_Table as @praxisui/table<br>(praxis-table.ts)
|
|
107
|
+
participant Crud_Service as @praxisui/core<br>(generic-crud.service.ts)
|
|
108
|
+
participant BE_Controller as Backend Controller<br>(DepartamentoController.java)
|
|
109
|
+
participant Abstract_Controller as AbstractCrudController
|
|
110
|
+
|
|
111
|
+
FE_Component->>Praxis_Table: Usa o componente com <br> <praxis-table resourcePath="human-resources/departamentos">
|
|
112
|
+
|
|
113
|
+
activate Praxis_Table
|
|
114
|
+
Praxis_Table->>Praxis_Table: ngOnChanges() detecta o @Input() resourcePath
|
|
115
|
+
Praxis_Table->>Crud_Service: 1. Chama crudService.configure("human-resources/departamentos")
|
|
116
|
+
|
|
117
|
+
activate Crud_Service
|
|
118
|
+
Crud_Service->>Crud_Service: Armazena o resourcePath
|
|
119
|
+
deactivate Crud_Service
|
|
120
|
+
|
|
121
|
+
Praxis_Table->>Praxis_Table: 2. Chama this.loadSchema()
|
|
122
|
+
Praxis_Table->>Crud_Service: Chama crudService.getSchema()
|
|
123
|
+
|
|
124
|
+
activate Crud_Service
|
|
125
|
+
Crud_Service->>Crud_Service: getEndpointUrl('schema') constrói a URL: <br> "/api/human-resources/departamentos/schemas"
|
|
126
|
+
Crud_Service->>BE_Controller: Requisição HTTP GET para .../schemas
|
|
127
|
+
deactivate Crud_Service
|
|
128
|
+
|
|
129
|
+
activate BE_Controller
|
|
130
|
+
Note over BE_Controller: @RequestMapping("/human-resources/departamentos")
|
|
131
|
+
BE_Controller->>Abstract_Controller: Herda o método que lida com @GetMapping("/schemas")
|
|
132
|
+
|
|
133
|
+
activate Abstract_Controller
|
|
134
|
+
Abstract_Controller->>Abstract_Controller: Gera e retorna o Schema da UI
|
|
135
|
+
Abstract_Controller-->>BE_Controller: Retorna o Schema
|
|
136
|
+
deactivate Abstract_Controller
|
|
137
|
+
|
|
138
|
+
BE_Controller-->>Crud_Service: Resposta HTTP com o JSON do Schema
|
|
139
|
+
deactivate BE_Controller
|
|
140
|
+
|
|
141
|
+
activate Crud_Service
|
|
142
|
+
Crud_Service-->>Praxis_Table: Retorna um Observable com as<br>definições de colunas (FieldDefinition[])
|
|
143
|
+
deactivate Crud_Service
|
|
144
|
+
|
|
145
|
+
Praxis_Table->>Praxis_Table: Constrói as colunas da tabela<br>a partir do schema recebido
|
|
146
|
+
|
|
147
|
+
Praxis_Table->>Praxis_Table: 3. Chama this.fetchData()
|
|
148
|
+
Praxis_Table->>Crud_Service: Chama crudService.filter(...) para buscar dados
|
|
149
|
+
|
|
150
|
+
activate Crud_Service
|
|
151
|
+
Crud_Service->>Crud_Service: getEndpointUrl('filter') constrói a URL: <br> "/api/human-resources/departamentos/filter"
|
|
152
|
+
Crud_Service->>BE_Controller: Requisição HTTP POST para .../filter
|
|
153
|
+
deactivate Crud_Service
|
|
154
|
+
|
|
155
|
+
activate BE_Controller
|
|
156
|
+
BE_Controller->>Abstract_Controller: Herda o método que lida com @PostMapping("/filter")
|
|
157
|
+
|
|
158
|
+
activate Abstract_Controller
|
|
159
|
+
Abstract_Controller->>Abstract_Controller: Processa a requisição e busca os dados
|
|
160
|
+
Abstract_Controller-->>BE_Controller: Retorna os dados
|
|
161
|
+
deactivate Abstract_Controller
|
|
162
|
+
|
|
163
|
+
BE_Controller-->>Crud_Service: Resposta HTTP com os dados (Page<DepartamentoDTO>)
|
|
164
|
+
deactivate BE_Controller
|
|
165
|
+
|
|
166
|
+
activate Crud_Service
|
|
167
|
+
Crud_Service-->>Praxis_Table: Retorna um Observable com os dados
|
|
168
|
+
deactivate Crud_Service
|
|
169
|
+
|
|
170
|
+
Praxis_Table->>Praxis_Table: Atualiza o dataSource da tabela com os dados recebidos
|
|
171
|
+
deactivate Praxis_Table
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## ℹ️ Dicas de UX (Tabela)
|
|
175
|
+
|
|
176
|
+
- Multi‑sort: habilite em Comportamento → Ordenação. No uso, mantenha Ctrl/Cmd pressionado ao clicar em múltiplas colunas.
|
|
177
|
+
- Duplo clique na linha: configure em Comportamento → Interação (habilitar e escolha a ação: editar/visualizar/personalizada).
|
|
178
|
+
- Virtualização: habilite em Comportamento → Virtualização para listas grandes (requer altura de linha previsível).
|
|
179
|
+
- Densidade e linhas de grade: ajuste em Comportamento → Aparência rápida (compacta/confortável/espaçosa; bordas horizontais/verticais).
|
|
180
|
+
|
|
181
|
+
## Virtualização (CDK)
|
|
182
|
+
|
|
183
|
+
Quando `behavior.virtualization.enabled` estiver ativo, as linhas da tabela são renderizadas com `cdk-virtual-scroll-viewport` (cabeçalho permanece igual).
|
|
184
|
+
|
|
185
|
+
- Propriedades:
|
|
186
|
+
- `itemHeight`: altura da linha (px); padrão 44.
|
|
187
|
+
- `bufferSize`: itens adicionais de buffer.
|
|
188
|
+
- `minContainerHeight`: altura mínima do viewport (px ou CSS, ex.: `320` ou `50vh`).
|
|
189
|
+
- `strategy`: `fixed` | `dynamic` (atual uso visual não altera lógica de medição).
|
|
190
|
+
- Observação: sticky/pinned em colunas pode ter limitações em conjunto com virtual scroll (mantido sob feature flag; caminho não‑virtual preservado como fallback).
|
|
191
|
+
|
|
192
|
+
## Paginação (posição/estilo)
|
|
193
|
+
|
|
194
|
+
### Concept Usage
|
|
195
|
+
|
|
196
|
+
- [Schema‑driven UI](../../../../docs/concepts/schema-driven-ui.md)
|
|
197
|
+
- [Self‑describing APIs](../../../../docs/concepts/self-describing-apis.md)
|
|
198
|
+
- [Rules Engines & Specifications](../../../../docs/concepts/rules-engines-and-specifications.md)
|
|
199
|
+
- [Configuration‑driven development](../../../../docs/concepts/configuration-driven-development.md)
|
|
200
|
+
|
|
201
|
+
- `behavior.pagination.position`: `top` | `bottom` | `both`.
|
|
202
|
+
- `behavior.pagination.style`: `default` | `compact` (aplica classe de estilo no paginator).
|
|
203
|
+
|
|
204
|
+
## Duplo clique na linha
|
|
205
|
+
|
|
206
|
+
Ative em Comportamento → Interação.
|
|
207
|
+
|
|
208
|
+
- Output: `rowDoubleClick` com payload `{ action: string; row: any }`.
|
|
209
|
+
- Ação: `edit` | `view` | `custom` (quando `custom`, use `customAction` no Behavior para definir o identificador).
|
|
210
|
+
|
|
211
|
+
Exemplo de uso (template do host):
|
|
212
|
+
|
|
213
|
+
```html
|
|
214
|
+
<praxis-table
|
|
215
|
+
[config]="tableConfig"
|
|
216
|
+
[resourcePath]="'human-resources/departamentos'"
|
|
217
|
+
(rowDoubleClick)="onRowDoubleClick($event)">
|
|
218
|
+
</praxis-table>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
onRowDoubleClick(evt: { action: string; row: any }) {
|
|
223
|
+
if (evt.action === 'edit') {
|
|
224
|
+
// abrir editor
|
|
225
|
+
} else if (evt.action === 'view') {
|
|
226
|
+
// abrir detalhe somente leitura
|
|
227
|
+
} else {
|
|
228
|
+
// ação customizada
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Ordenação inicial (defaultSort)
|
|
234
|
+
|
|
235
|
+
- Configure em `behavior.sorting.defaultSort`.
|
|
236
|
+
- Aceita objeto único (coluna/direção) ou array para multi‑sort.
|
|
237
|
+
|
|
238
|
+
Exemplos:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
sorting: {
|
|
242
|
+
enabled: true,
|
|
243
|
+
strategy: 'server',
|
|
244
|
+
multiSort: false,
|
|
245
|
+
defaultSort: { column: 'nome', direction: 'asc' },
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
sorting: {
|
|
251
|
+
enabled: true,
|
|
252
|
+
strategy: 'server',
|
|
253
|
+
multiSort: true,
|
|
254
|
+
defaultSort: [
|
|
255
|
+
{ column: 'status', direction: 'desc' },
|
|
256
|
+
{ column: 'nome', direction: 'asc' },
|
|
257
|
+
],
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Observação: quando informado, o defaultSort é aplicado na carga inicial se não houver estado de ordenação ativo.
|
|
262
|
+
|
|
263
|
+
## Coluna de ações (sticky)
|
|
264
|
+
|
|
265
|
+
- Fixe a coluna de ações no início/fim configurando `actions.row.sticky`:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
actions: {
|
|
269
|
+
row: {
|
|
270
|
+
enabled: true,
|
|
271
|
+
sticky: 'end', // true | 'start' | 'end'
|
|
272
|
+
width: '120px',
|
|
273
|
+
actions: [ /* ... */ ],
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Observação: em modo virtualizado, sticky pode ter limitações dependendo do layout.
|
|
279
|
+
|
|
280
|
+
## Colunas de dados (sticky)
|
|
281
|
+
|
|
282
|
+
Fixe colunas específicas no início/fim usando `columns[].sticky`:
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
columns: [
|
|
286
|
+
{ field: 'id', header: 'ID', width: '80px', sticky: 'start' },
|
|
287
|
+
{ field: 'nome', header: 'Nome' },
|
|
288
|
+
{ field: 'status', header: 'Status', sticky: 'end' },
|
|
289
|
+
]
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
- Valores aceitos: `true` | `'start'` | `'end'`.
|
|
293
|
+
- `true` equivale a `'start'` (fixa no início).
|
|
294
|
+
- Dica: combine com `width` para evitar jitter de layout.
|
|
295
|
+
|
|
296
|
+
Onde configurar no Editor Visual:
|
|
297
|
+
|
|
298
|
+
- Aba “Colunas” → grupo “Posição Fixa”: escolha entre Nenhum / Início / Fim.
|
|
299
|
+
- Para a coluna de ações, use a aba “Barra de Ferramentas & Ações” → grupo “Coluna de Ações” → “Fixar coluna de ações”.
|
|
300
|
+
|
|
301
|
+
## 🔒 Verificação de Schema (ETag) e Notificações (somente em customização)
|
|
302
|
+
|
|
303
|
+
Quando já existe uma configuração salva (colunas presentes), a tabela não baixa o schema bruto do servidor para montar as colunas. Em vez disso, valida se há uma nova versão do schema usando ETag/If-None-Match. Notificações são exibidas somente quando o modo de customização está ativo (`editModeEnabled = true`).
|
|
304
|
+
|
|
305
|
+
### Comportamento
|
|
306
|
+
|
|
307
|
+
- Primeira vez (sem colunas):
|
|
308
|
+
- Baixa o schema (200), gera as colunas e persiste `config.meta` (incluindo `schemaId`, `serverHash`, `lastVerifiedAt`).
|
|
309
|
+
- Próximas vezes (colunas já existentes):
|
|
310
|
+
- Faz uma verificação leve em `/schemas/filtered` com `If-None-Match: "<serverHash>"`.
|
|
311
|
+
- 304 Not Modified: atualiza `config.meta.lastVerifiedAt` e segue usando a configuração local.
|
|
312
|
+
- 200 OK (hash mudou): atualiza `config.meta.serverHash/lastVerifiedAt`, marca estado `schemaOutdated=true` e (em modo de customização) exibe aviso e CTA para reconciliar. O schema bruto não é usado/armazenado nesta etapa.
|
|
313
|
+
|
|
314
|
+
### Diagrama: Verificação de Schema via ETag
|
|
315
|
+
|
|
316
|
+
```mermaid
|
|
317
|
+
sequenceDiagram
|
|
318
|
+
autonumber
|
|
319
|
+
participant PT as PraxisTable
|
|
320
|
+
participant GS as GenericCrudService (@praxisui/core)
|
|
321
|
+
participant API as Backend (/schemas/filtered)
|
|
322
|
+
|
|
323
|
+
opt Primeira vez (sem colunas)
|
|
324
|
+
PT->>GS: getSchema() (gera colunas)
|
|
325
|
+
GS->>API: GET /schemas/filtered (sem If-None-Match)
|
|
326
|
+
API-->>GS: 200 + ETag/X-Schema-Hash
|
|
327
|
+
GS-->>PT: FieldDefinition[] (normalized)
|
|
328
|
+
PT->>PT: Persiste config + meta (serverHash, lastVerifiedAt)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
opt Próximas vezes (colunas existentes)
|
|
332
|
+
PT->>API: GET /schemas/filtered?path=... (If-None-Match: "<serverHash>")
|
|
333
|
+
alt Igual (sem mudanças)
|
|
334
|
+
API-->>PT: 304 Not Modified (sem body)
|
|
335
|
+
PT->>PT: Atualiza lastVerifiedAt; segue usando config
|
|
336
|
+
else Diferente (mudou)
|
|
337
|
+
API-->>PT: 200 OK (com ETag/X-Schema-Hash)
|
|
338
|
+
PT->>PT: Atualiza serverHash + lastVerifiedAt; schemaOutdated=true
|
|
339
|
+
Note over PT: Em customização: mostrar banner/snackbar + badge e CTA “Reconciliar”
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Inputs/Outputs
|
|
345
|
+
|
|
346
|
+
- Inputs (efetivos apenas quando `editModeEnabled = true`):
|
|
347
|
+
- `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'`
|
|
348
|
+
- `snoozeMs: number = 86400000` (24h)
|
|
349
|
+
- `autoOpenSettingsOnOutdated: boolean = false`
|
|
350
|
+
- Output:
|
|
351
|
+
- `schemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; resourcePath?: string }`
|
|
352
|
+
|
|
353
|
+
### Fallback Global (opcional)
|
|
354
|
+
|
|
355
|
+
- Quando os inputs do widget permanecem com os valores padrão da biblioteca e não há overrides locais equivalentes, a Tabela aplica como último fallback as preferências globais lidas via `GlobalConfigService.getSchemaPrefsGlobal()`.
|
|
356
|
+
- Cadeia de precedência (inalterada): `@Inputs (widget)` → Prefs do widget → Prefs da página → Prefs globais → Defaults.
|
|
357
|
+
- Não há persistência no widget a partir das globais; o fallback é aplicado somente em memória.
|
|
358
|
+
|
|
359
|
+
### Persistência por hash (ConfigStorage)
|
|
360
|
+
|
|
361
|
+
- `schemaIgnore:{tableId}:{serverHash}` → ignora avisos para este hash.
|
|
362
|
+
- `schemaSnooze:{tableId}:{serverHash}` → ISODate até quando não avisar (lembrar depois).
|
|
363
|
+
- `schemaNotified:{tableId}:{serverHash}` → evita snackbar duplicado.
|
|
364
|
+
|
|
365
|
+
### UX
|
|
366
|
+
|
|
367
|
+
- Banner inline (acima da tabela) quando `schemaOutdated=true`, com ações:
|
|
368
|
+
- Reconciliar (abre Configurações)
|
|
369
|
+
- Lembrar depois (snooze)
|
|
370
|
+
- Ignorar (silenciar para este hash)
|
|
371
|
+
- Snackbar opcional (uma vez por hash) com ação “Reconciliar”.
|
|
372
|
+
- Badge “!” na engrenagem com tooltip contextual: “Schema do servidor mudou — Reconciliar”.
|
|
373
|
+
|
|
374
|
+
### Exemplo de uso
|
|
375
|
+
|
|
376
|
+
```html
|
|
377
|
+
<praxis-table
|
|
378
|
+
[editModeEnabled]="true"
|
|
379
|
+
[notifyIfOutdated]="'both'"
|
|
380
|
+
[snoozeMs]="12 * 60 * 60 * 1000"
|
|
381
|
+
[autoOpenSettingsOnOutdated]="false"
|
|
382
|
+
(schemaStatusChange)="onSchemaStatus($event)"
|
|
383
|
+
resourcePath="retaguarda/tipos-servidor"
|
|
384
|
+
[config]="tableConfig">
|
|
385
|
+
</praxis-table>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
onSchemaStatus(ev: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string }) {
|
|
390
|
+
if (ev.outdated) {
|
|
391
|
+
console.log('Schema mudou. Hash:', ev.serverHash);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Notas
|
|
397
|
+
|
|
398
|
+
- A verificação leve não grava nem usa o schema bruto quando `config.columns` já existe.
|
|
399
|
+
- Em ambientes sem customização (`editModeEnabled=false`), não há notificações visuais; ainda assim `schemaStatusChange` é emitido e `config.meta.lastVerifiedAt` atualizado.
|
|
400
|
+
|
|
401
|
+
## Filtro Avançado (PraxisFilter)
|
|
402
|
+
|
|
403
|
+
### Boas práticas para estabilidade
|
|
404
|
+
|
|
405
|
+
- Evite literais em bindings para o carregador dinâmico:
|
|
406
|
+
- Não use `[fields]="[quickFieldMeta]"`; prefira um array estável como `quickFieldMetaArray` atualizado apenas quando o metadata mudar.
|
|
407
|
+
- Reutilize `FormGroup` quando possível. Se precisar trocar o `FormGroup` sem mudar os campos, deixe o `DynamicFieldLoader` reatribuir os controles (rebind-only) em vez de recriar componentes.
|
|
408
|
+
- Evite recriar arrays/objetos desnecessariamente em `ngOnChanges`/`ngAfterViewInit`. Prefira atualizar valores (`setValue`, `patchValue`) e manter referências estáveis.
|
|
409
|
+
- Observers (Resize/Mutation): atualize `overlayOrigin` e largura apenas quando houver mudanças reais; isso evita loops de CD.
|
|
410
|
+
|
|
411
|
+
### Diagnóstico e Logs
|
|
412
|
+
|
|
413
|
+
- O `DynamicFieldLoader` possui logs de diagnóstico que podem ser habilitados em tempo de execução definindo `window.__PRAXIS_DEBUG_DFL__ = true` no console do navegador.
|
|
414
|
+
- Quando desabilitado (default), o carregador reduz a verbosidade de logs.
|
|
415
|
+
|
|
416
|
+
### Comportamento do carregador (v20+)
|
|
417
|
+
|
|
418
|
+
- Guardas internas evitam re-renderizações desnecessárias:
|
|
419
|
+
- Se o snapshot do conteúdo dos campos (nome + controlType) for idêntico e o `FormGroup` não tiver mudado, a renderização é ignorada.
|
|
420
|
+
- Se apenas o `FormGroup` mudou (mesmo snapshot de campos), os controles são reatribuídos aos componentes existentes (rebind-only), preservando o estado visual e evitando recriações.
|
|
421
|
+
|
|
422
|
+
### Testes
|
|
423
|
+
|
|
424
|
+
- Há testes cobrindo:
|
|
425
|
+
- Reatribuição de `FormControl` em troca de `FormGroup` (select/multiselect/autocomplete).
|
|
426
|
+
- Ignorar re-render quando apenas a referência do array muda mas o conteúdo é igual.
|
|
427
|
+
- Recriar componentes quando `controlType` muda.
|
|
428
|
+
|
|
429
|
+
### Fluxo de Schema e Metadados
|
|
430
|
+
|
|
431
|
+
- O `PraxisFilter` busca schema do DTO de filtro via `/schemas/filtered` com ETag/If-None-Match
|
|
432
|
+
e emite `metaChanged` com `{ schemaId, serverHash, context }`.
|
|
433
|
+
- O schema é normalizado e `advancedConfig.metadata` é preenchido para auditoria/telemetria.
|
|
434
|
+
- Detalhes: `docs/schemas/fluxo-schema.md` (cliente/caching, 200/304, reconciliadores Form/Filter).
|
|
435
|
+
|
|
436
|
+
### Resolução da chave primária (idField)
|
|
437
|
+
|
|
438
|
+
- O backend anota o schema com `x-ui.resource.idField` (e `idFieldValid`) via `/schemas/filtered`.
|
|
439
|
+
- A tabela adota o campo identificador automaticamente com a seguinte precedência:
|
|
440
|
+
- `@Input() idField` → `crudContext.idField` → `config.meta.idField` (persistido) → `GenericCrudService.getResourceIdField()` (derivado do schema) → `'id'`.
|
|
441
|
+
- Se `config.meta.idField` divergir do servidor, o componente alerta o usuário e mantém o valor do TableConfig até reconciliação.
|
|
442
|
+
- A resolução ocorre no `loadSchema()` e também é considerada em tempo de execução para evitar corridas.
|
|
443
|
+
- Para recursos cuja PK não é `id`, defina `getIdFieldName()` no controller backend correspondente.
|
|
444
|
+
|
|
445
|
+
### Diagramas
|
|
446
|
+
|
|
447
|
+
Fluxo (Grid): adoção de schema e idField
|
|
448
|
+
|
|
449
|
+
```mermaid
|
|
450
|
+
sequenceDiagram
|
|
451
|
+
autonumber
|
|
452
|
+
participant PT as PraxisTable
|
|
453
|
+
participant GS as GenericCrudService
|
|
454
|
+
participant API as Backend
|
|
455
|
+
|
|
456
|
+
PT->>GS: configure(resourcePath)
|
|
457
|
+
PT->>GS: getSchema()
|
|
458
|
+
GS->>API: GET {resource}/schemas → 302 → /schemas/filtered
|
|
459
|
+
API-->>GS: 200/304 schema + x-ui.resource.idField
|
|
460
|
+
GS->>GS: cache + lastResourceMeta.idField
|
|
461
|
+
GS-->>PT: FieldDefinition[] (normalizado)
|
|
462
|
+
Note over PT: idField = input || context || GS.getResourceIdField() || 'id'
|
|
463
|
+
PT->>PT: construir colunas e renderizar
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Fluxo (Delete): resolução do identificador
|
|
467
|
+
|
|
468
|
+
```mermaid
|
|
469
|
+
sequenceDiagram
|
|
470
|
+
autonumber
|
|
471
|
+
participant PT as PraxisTable
|
|
472
|
+
participant GS as GenericCrudService
|
|
473
|
+
participant API as Backend
|
|
474
|
+
|
|
475
|
+
PT->>PT: key = getIdField() (precedência)
|
|
476
|
+
PT->>PT: id = row[key] || row['id'] || heurísticas
|
|
477
|
+
PT->>GS: delete(id)
|
|
478
|
+
GS->>API: DELETE {resource}/{id}
|
|
479
|
+
API-->>GS: 204 No Content
|
|
480
|
+
GS-->>PT: sucesso → refresh
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Troubleshooting rápido (idField)
|
|
484
|
+
|
|
485
|
+
- A ação delete falhou por ID ausente: verifique se o schema contém `x-ui.resource.idField` e se a coluna correspondente existe no dataset.
|
|
486
|
+
- O ID está em outra propriedade: defina `@Input() idField` ou `crudContext.idField` temporariamente; ajuste o backend com `getIdFieldName()` para persistir o comportamento.
|
|
487
|
+
- Cache 304 sem idField aplicado: confirme que o serviço recebeu o body pelo menos uma vez (200) e que `GenericCrudService.getResourceIdField()` retorna o valor esperado.
|
|
488
|
+
|
|
489
|
+
### Uso com Dados Locais (Client-Side)
|
|
490
|
+
|
|
491
|
+
Se você precisar fornecer os dados manualmente (por exemplo, de uma fonte que não é uma API Praxis), pode usar o input `[data]` e omitir o `resourcePath`. Neste modo, todas as operações (paginação, ordenação, filtro) são realizadas no lado do cliente.
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
import { PraxisTable } from "@praxisui/table";
|
|
495
|
+
import { TableConfig } from "@praxisui/core";
|
|
496
|
+
|
|
497
|
+
@Component({
|
|
498
|
+
selector: "app-example",
|
|
499
|
+
standalone: true,
|
|
500
|
+
imports: [PraxisTable],
|
|
501
|
+
template: ` <praxis-table [config]="tableConfig" [data]="employees"> </praxis-table> `,
|
|
502
|
+
})
|
|
503
|
+
export class ExampleComponent {
|
|
504
|
+
// Configuração de colunas e comportamento é obrigatória neste modo
|
|
505
|
+
tableConfig: TableConfig = {
|
|
506
|
+
columns: [
|
|
507
|
+
{ field: "id", header: "ID", type: "number" },
|
|
508
|
+
{ field: "name", header: "Nome", type: "string" },
|
|
509
|
+
{ field: "email", header: "Email", type: "string" },
|
|
510
|
+
],
|
|
511
|
+
behavior: {
|
|
512
|
+
pagination: { enabled: true, pageSize: 10 },
|
|
513
|
+
sorting: { enabled: true },
|
|
514
|
+
filtering: { enabled: true },
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
employees = [
|
|
519
|
+
{ id: 1, name: "João Silva", email: "joao@empresa.com" },
|
|
520
|
+
{ id: 2, name: "Maria Santos", email: "maria@empresa.com" },
|
|
521
|
+
// ... mais dados
|
|
522
|
+
];
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## ⚙️ Fluxo de Paginação e Filtros (Server-Side)
|
|
527
|
+
|
|
528
|
+
Quando a `<praxis-table>` é conectada a um `resourcePath`, as operações de paginação, ordenação e filtro são delegadas ao backend. Isso garante alta performance, pois apenas os dados visíveis na tela são trafegados pela rede.
|
|
529
|
+
|
|
530
|
+
O diagrama abaixo detalha a sequência de eventos, desde a interação do usuário na UI até a construção da consulta JPA no servidor.
|
|
531
|
+
|
|
532
|
+
```mermaid
|
|
533
|
+
sequenceDiagram
|
|
534
|
+
participant UI as @praxisui/table (UI)
|
|
535
|
+
participant CrudService as @praxisui/core (GenericCrudService)
|
|
536
|
+
participant Controller as Backend (AbstractCrudController)
|
|
537
|
+
participant Service as Backend (BaseCrudService)
|
|
538
|
+
participant SpecBuilder as Backend (GenericSpecificationsBuilder)
|
|
539
|
+
participant Repository as Spring Data JPA (JpaSpecificationExecutor)
|
|
540
|
+
|
|
541
|
+
UI->>UI: Usuário clica na página 2 e<br>digita "Tech" no filtro de nome.
|
|
542
|
+
|
|
543
|
+
UI->>UI: onPageChange({pageIndex: 1, pageSize: 10})<br>onFilterChange({nome: 'Tech'})
|
|
544
|
+
|
|
545
|
+
UI->>UI: Chama fetchData() com:<br>filterCriteria = {nome: 'Tech'}<br>pageable = {pageNumber: 1, pageSize: 10, sort: 'nome,asc'}
|
|
546
|
+
|
|
547
|
+
UI->>CrudService: Chama .filter({nome: 'Tech'}, pageable)
|
|
548
|
+
|
|
549
|
+
activate CrudService
|
|
550
|
+
CrudService->>CrudService: Cria HttpParams:<br>page=1, size=10, sort=nome,asc
|
|
551
|
+
CrudService->>Controller: Requisição POST para /api/.../filter<br>Body: {nome: 'Tech'}<br>Params: ?page=1&size=10&sort=nome,asc
|
|
552
|
+
deactivate CrudService
|
|
553
|
+
|
|
554
|
+
activate Controller
|
|
555
|
+
Controller->>Controller: Spring injeta o corpo no FilterDTO<br>e os params no objeto Pageable.
|
|
556
|
+
Controller->>Service: Chama .filter(filterDTO, pageable)
|
|
557
|
+
deactivate Controller
|
|
558
|
+
|
|
559
|
+
activate Service
|
|
560
|
+
Service->>SpecBuilder: Chama .buildSpecification(filterDTO)
|
|
561
|
+
|
|
562
|
+
activate SpecBuilder
|
|
563
|
+
SpecBuilder->>SpecBuilder: Itera nos campos do FilterDTO.<br>Encontra @Filterable no campo 'nome'.
|
|
564
|
+
SpecBuilder->>SpecBuilder: Cria um Predicate JPA:<br> `criteriaBuilder.like(root.get("nome"), "%tech%")`
|
|
565
|
+
SpecBuilder->>Service: Retorna a Specification JPA construída.
|
|
566
|
+
deactivate SpecBuilder
|
|
567
|
+
|
|
568
|
+
Service->>Repository: Chama .findAll(specification, pageable)
|
|
569
|
+
|
|
570
|
+
activate Repository
|
|
571
|
+
Repository->>Repository: Spring Data JPA executa a<br>query SQL com WHERE, LIMIT, OFFSET, ORDER BY.
|
|
572
|
+
Repository-->>Service: Retorna um objeto Page<Entity> do BD.
|
|
573
|
+
deactivate Repository
|
|
574
|
+
|
|
575
|
+
Service-->>Controller: Retorna o Page<Entity>
|
|
576
|
+
deactivate Service
|
|
577
|
+
|
|
578
|
+
activate Controller
|
|
579
|
+
Controller->>Controller: Mapeia Page<Entity> para Page<DTO><br>e encapsula em RestApiResponse.
|
|
580
|
+
Controller-->>CrudService: Resposta HTTP 200 com<br>JSON do RestApiResponse.
|
|
581
|
+
deactivate Controller
|
|
582
|
+
|
|
583
|
+
activate CrudService
|
|
584
|
+
CrudService-->>UI: Retorna Observable<Page<DTO>>
|
|
585
|
+
deactivate CrudService
|
|
586
|
+
|
|
587
|
+
UI->>UI: Atualiza a tabela com os novos dados e o paginador.
|
|
588
|
+
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Pontos-Chave do Fluxo:
|
|
592
|
+
|
|
593
|
+
1. **UI (`@praxisui/table`)**: Captura eventos do usuário e os traduz em objetos `filterCriteria` e `pageable`.
|
|
594
|
+
2. **Serviço Frontend (`@praxisui/core`)**: O `GenericCrudService` serializa o `pageable` como parâmetros de URL e o `filterCriteria` como corpo de uma requisição POST.
|
|
595
|
+
3. **Controller Backend**: O `AbstractCrudController` recebe a requisição. O Spring Boot automaticamente popula o DTO de filtro com o corpo da requisição e o objeto `Pageable` com os parâmetros da URL.
|
|
596
|
+
4. **Serviço Backend (`praxis-metadata-core`)**: O `GenericSpecificationsBuilder` inspeciona as anotações `@Filterable` no DTO de filtro para construir uma `Specification` JPA dinâmica.
|
|
597
|
+
5. **Repositório (Spring Data JPA)**: O `JpaSpecificationExecutor` (geralmente estendido pelo seu repositório) usa a `Specification` e o `Pageable` para gerar e executar a consulta SQL final, otimizada para o banco de dados.
|
|
598
|
+
|
|
599
|
+
## 🎨 Edição Visual da Tabela: O Poder do Low-Code
|
|
600
|
+
|
|
601
|
+
A `<praxis-table>` vem com um poderoso editor de configuração visual que permite personalizar quase todos os aspectos da sua tabela em tempo real, sem escrever uma única linha de código. Ative o editor passando a propriedade `[editModeEnabled]="true"` para o componente.
|
|
602
|
+
|
|
603
|
+
A seguir, veja os principais recursos que você pode configurar visualmente:
|
|
604
|
+
|
|
605
|
+
### 1. Gerenciamento de Colunas
|
|
606
|
+
|
|
607
|
+
Controle total sobre as colunas da sua tabela. Dentro do editor, você pode:
|
|
608
|
+
|
|
609
|
+
- **Reordenar com Arrastar e Soltar:** Simplesmente clique e arraste uma coluna para a posição desejada.
|
|
610
|
+
- **Alterar Visibilidade:** Use a caixa de seleção ao lado de cada coluna para mostrá-la ou ocultá-la instantaneamente.
|
|
611
|
+
- **Editar Títulos e Largura:** Clique em uma coluna para abrir suas propriedades e altere o texto do cabeçalho, defina uma largura fixa (ex: `150px`) ou deixe-a automática.
|
|
612
|
+
|
|
613
|
+
### 2. Transformação de Dados Sem Código
|
|
614
|
+
|
|
615
|
+
Converta dados brutos em informações claras e formatadas para o usuário.
|
|
616
|
+
|
|
617
|
+
- **Formatação Automática:** Selecione uma coluna e escolha seu "Tipo de Dado". Se escolher `Moeda`, os valores serão formatados como `R$ 1.234,56`. Se escolher `Data`, você pode selecionar formatos como `dd/MM/yyyy` ou `25 de janeiro de 2025`.
|
|
618
|
+
- **Mapeamento de Valores:** Transforme códigos e valores brutos em texto legível. Na seção "Mapeamento de Valores", você pode definir visualmente que o valor `true` deve ser exibido como "Ativo" e `false` como "Inativo", ou que o código `1` significa "Pendente" e `2` significa "Aprovado".
|
|
619
|
+
|
|
620
|
+
### 3. Colunas Calculadas com Fórmulas Visuais
|
|
621
|
+
|
|
622
|
+
Crie novas colunas dinamicamente a partir de dados existentes, sem precisar programar.
|
|
623
|
+
|
|
624
|
+
- **Concatenar Texto:** Crie uma "Coluna Calculada", escolha a fórmula "Concatenar" e selecione os campos `nome` e `sobrenome` para criar uma coluna "Nome Completo".
|
|
625
|
+
- **Realizar Operações Matemáticas:** Use a fórmula "Operação Matemática" para criar uma coluna que calcula `preço * quantidade`.
|
|
626
|
+
- **Criar Valores Condicionais (IF/ELSE):** Com a fórmula "Condicional", você pode criar uma coluna "Nível de Risco" que exibe "Alto" se o `valor` for maior que 1000, e "Baixo" caso contrário.
|
|
627
|
+
|
|
628
|
+
### 4. Formatação Condicional (Regras de Estilo)
|
|
629
|
+
|
|
630
|
+
Destaque informações importantes aplicando estilos que mudam com base nos dados da linha.
|
|
631
|
+
|
|
632
|
+
- **Crie Regras Visuais:** Na seção de "Formatação Condicional", crie uma nova regra.
|
|
633
|
+
- **Defina a Condição:** Estabeleça a condição, por exemplo: "Quando a coluna `status` tiver o valor igual a 'Urgente'".
|
|
634
|
+
- **Aplique o Estilo:** Use seletores de cor para definir que, quando a condição for verdadeira, a cor de fundo da célula ou da linha inteira deve se tornar vermelha e o texto, branco.
|
|
635
|
+
|
|
636
|
+
### 5. Comportamentos da Tabela
|
|
637
|
+
|
|
638
|
+
Habilite e configure as funcionalidades centrais da tabela com um clique. Na aba "Comportamento", você pode:
|
|
639
|
+
|
|
640
|
+
- **Ativar/Desativar Paginação:** Com um único interruptor, ative a paginação para tabelas com muitos dados e defina quantos itens exibir por página.
|
|
641
|
+
- **Controlar Ordenação e Filtros:** Habilite a capacidade dos usuários de ordenar colunas e filtrar os dados com simples caixas de seleção.
|
|
642
|
+
- **Gerenciar Seleção de Linhas:** Permita que os usuários selecionem uma ou várias linhas para realizar ações em lote.
|
|
643
|
+
|
|
644
|
+
### Editores Especializados
|
|
645
|
+
|
|
646
|
+
#### Behavior Editor
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
import { BehaviorConfigEditorComponent } from '@praxisui/table';
|
|
650
|
+
|
|
651
|
+
// Usar como componente standalone para edição específica
|
|
652
|
+
<behavior-config-editor
|
|
653
|
+
[config]="tableConfig"
|
|
654
|
+
(configChange)="onBehaviorChange($event)">
|
|
655
|
+
</behavior-config-editor>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
#### Columns Editor
|
|
659
|
+
|
|
660
|
+
```typescript
|
|
661
|
+
import { ColumnsConfigEditorComponent } from '@praxisui/table';
|
|
662
|
+
|
|
663
|
+
<columns-config-editor
|
|
664
|
+
[config]="tableConfig"
|
|
665
|
+
(configChange)="onColumnsChange($event)"
|
|
666
|
+
(columnChange)="onColumnChange($event)">
|
|
667
|
+
</columns-config-editor>
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## 🔧 Configuração Avançada
|
|
671
|
+
|
|
672
|
+
### Performance com Virtualização
|
|
673
|
+
|
|
674
|
+
```typescript
|
|
675
|
+
const highVolumeConfig: TableConfig = {
|
|
676
|
+
columns: [...],
|
|
677
|
+
performance: {
|
|
678
|
+
virtualization: {
|
|
679
|
+
enabled: true,
|
|
680
|
+
itemHeight: 48,
|
|
681
|
+
bufferSize: 10,
|
|
682
|
+
minContainerHeight: 400,
|
|
683
|
+
strategy: 'fixed'
|
|
684
|
+
},
|
|
685
|
+
lazyLoading: {
|
|
686
|
+
threshold: 100,
|
|
687
|
+
images: true,
|
|
688
|
+
components: true
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### Acessibilidade Personalizada
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
const accessibleConfig: TableConfig = {
|
|
698
|
+
columns: [...],
|
|
699
|
+
accessibility: {
|
|
700
|
+
enabled: true,
|
|
701
|
+
announcements: {
|
|
702
|
+
dataChanges: true,
|
|
703
|
+
userActions: true,
|
|
704
|
+
loadingStates: true,
|
|
705
|
+
liveRegion: 'polite'
|
|
706
|
+
},
|
|
707
|
+
keyboard: {
|
|
708
|
+
shortcuts: true,
|
|
709
|
+
tabNavigation: true,
|
|
710
|
+
arrowNavigation: true,
|
|
711
|
+
skipLinks: true,
|
|
712
|
+
focusTrap: false
|
|
713
|
+
},
|
|
714
|
+
highContrast: false,
|
|
715
|
+
reduceMotion: false
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Aparência Customizada
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
const styledConfig: TableConfig = {
|
|
724
|
+
columns: [...],
|
|
725
|
+
appearance: {
|
|
726
|
+
density: 'compact',
|
|
727
|
+
borders: {
|
|
728
|
+
showRowBorders: true,
|
|
729
|
+
showColumnBorders: false,
|
|
730
|
+
showOuterBorder: true,
|
|
731
|
+
style: 'solid',
|
|
732
|
+
width: 1,
|
|
733
|
+
color: '#e0e0e0'
|
|
734
|
+
},
|
|
735
|
+
elevation: {
|
|
736
|
+
level: 2,
|
|
737
|
+
shadowColor: 'rgba(0, 0, 0, 0.1)'
|
|
738
|
+
},
|
|
739
|
+
spacing: {
|
|
740
|
+
cellPadding: '8px 16px',
|
|
741
|
+
headerPadding: '12px 16px'
|
|
742
|
+
},
|
|
743
|
+
typography: {
|
|
744
|
+
fontWeight: '400',
|
|
745
|
+
fontSize: '14px',
|
|
746
|
+
headerFontWeight: '500',
|
|
747
|
+
headerFontSize: '14px'
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
## 🎯 Event Handling
|
|
754
|
+
|
|
755
|
+
### Eventos da Tabela
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
<praxis-table
|
|
759
|
+
[config]="tableConfig"
|
|
760
|
+
[data]="data"
|
|
761
|
+
(rowClick)="onRowClick($event)"
|
|
762
|
+
(rowSelect)="onRowSelect($event)"
|
|
763
|
+
(bulkAction)="onBulkAction($event)"
|
|
764
|
+
(configChange)="onConfigChange($event)"
|
|
765
|
+
(dataFilter)="onDataFilter($event)"
|
|
766
|
+
(dataSort)="onDataSort($event)"
|
|
767
|
+
(pageChange)="onPageChange($event)">
|
|
768
|
+
</praxis-table>
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### Implementação dos Handlers
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
export class MyComponent {
|
|
775
|
+
onRowClick(event: { row: any; index: number }) {
|
|
776
|
+
console.log("Row clicked:", event.row);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
onRowSelect(event: { selectedRows: any[]; isSelectAll: boolean }) {
|
|
780
|
+
console.log("Selection changed:", event.selectedRows);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
onBulkAction(event: { action: string; selectedRows: any[] }) {
|
|
784
|
+
switch (event.action) {
|
|
785
|
+
case "deleteSelected":
|
|
786
|
+
this.deleteMultiple(event.selectedRows);
|
|
787
|
+
break;
|
|
788
|
+
// Handle other bulk actions
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
onConfigChange(newConfig: TableConfig) {
|
|
793
|
+
this.tableConfig = newConfig;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
## 🛠️ Utilitários e Helpers
|
|
799
|
+
|
|
800
|
+
### Helper Functions
|
|
801
|
+
|
|
802
|
+
```typescript
|
|
803
|
+
import { createDefaultTableConfig, isValidTableConfig, cloneTableConfig, mergeTableConfigs } from "@praxisui/core";
|
|
804
|
+
|
|
805
|
+
// Criar configuração padrão
|
|
806
|
+
const defaultConfig = createDefaultTableConfig();
|
|
807
|
+
|
|
808
|
+
// Validar configuração
|
|
809
|
+
if (isValidTableConfig(myConfig)) {
|
|
810
|
+
// Configuração válida
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Clonar configuração
|
|
814
|
+
const clonedConfig = cloneTableConfig(originalConfig);
|
|
815
|
+
|
|
816
|
+
// Merge configurações
|
|
817
|
+
const mergedConfig = mergeTableConfigs(baseConfig, overrides);
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### Service Integration
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
import { TableConfigService } from '@praxisui/core';
|
|
824
|
+
|
|
825
|
+
@Component({...})
|
|
826
|
+
export class MyComponent {
|
|
827
|
+
constructor(private configService: TableConfigService) {}
|
|
828
|
+
|
|
829
|
+
ngOnInit() {
|
|
830
|
+
// Usar serviço para gerenciar configuração
|
|
831
|
+
this.configService.setConfig(this.tableConfig);
|
|
832
|
+
|
|
833
|
+
// Verificar recursos disponíveis
|
|
834
|
+
const hasMultiSort = this.configService.isFeatureEnabled('multiSort');
|
|
835
|
+
const hasBulkActions = this.configService.isFeatureEnabled('bulkActions');
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
## 🧪 Testes
|
|
841
|
+
|
|
842
|
+
### Unit Tests
|
|
843
|
+
|
|
844
|
+
```typescript
|
|
845
|
+
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
|
846
|
+
import { PraxisTable } from "@praxisui/table";
|
|
847
|
+
import { TableConfig } from "@praxisui/core";
|
|
848
|
+
|
|
849
|
+
describe("PraxisTable", () => {
|
|
850
|
+
let component: PraxisTable;
|
|
851
|
+
let fixture: ComponentFixture<PraxisTable>;
|
|
852
|
+
|
|
853
|
+
beforeEach(() => {
|
|
854
|
+
TestBed.configureTestingModule({
|
|
855
|
+
imports: [PraxisTable],
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
fixture = TestBed.createComponent(PraxisTable);
|
|
859
|
+
component = fixture.componentInstance;
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it("should create", () => {
|
|
863
|
+
expect(component).toBeTruthy();
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
it("should handle configuration changes", () => {
|
|
867
|
+
const config: TableConfig = {
|
|
868
|
+
columns: [{ field: "test", header: "Test" }],
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
component.config = config;
|
|
872
|
+
fixture.detectChanges();
|
|
873
|
+
|
|
874
|
+
expect(component.config).toEqual(config);
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
## 📋 Migration Guide
|
|
880
|
+
|
|
881
|
+
### Migração da Arquitetura V1/V2
|
|
882
|
+
|
|
883
|
+
Se você estava usando as versões anteriores com dual architecture, aqui estão as principais mudanças:
|
|
884
|
+
|
|
885
|
+
#### Imports Atualizados
|
|
886
|
+
|
|
887
|
+
```typescript
|
|
888
|
+
// Antes
|
|
889
|
+
import { TableConfigV1, TableConfigV2, TableConfigUnified } from "@praxisui/core";
|
|
890
|
+
|
|
891
|
+
// Depois
|
|
892
|
+
import { TableConfig } from "@praxisui/core";
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
#### Serviços Removidos
|
|
896
|
+
|
|
897
|
+
```typescript
|
|
898
|
+
// Antes
|
|
899
|
+
import { TableConfigAdapterService } from "@praxisui/table";
|
|
900
|
+
|
|
901
|
+
// Depois - Não mais necessário
|
|
902
|
+
// Uso direto da configuração
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
#### Tipos Simplificados
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
// Antes
|
|
909
|
+
config: TableConfigUnified;
|
|
910
|
+
|
|
911
|
+
// Depois
|
|
912
|
+
config: TableConfig;
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
### Breaking Changes
|
|
916
|
+
|
|
917
|
+
1. **TableConfigAdapterService**: Removido - uso direto da configuração
|
|
918
|
+
2. **TableConfigMigrationService**: Simplificado - funcionalidade integrada
|
|
919
|
+
3. **TableConfigUnified**: Renomeado para `TableConfig`
|
|
920
|
+
|
|
921
|
+
## 🔍 Troubleshooting
|
|
922
|
+
|
|
923
|
+
### Problemas Comuns
|
|
924
|
+
|
|
925
|
+
#### Configuração não está funcionando
|
|
926
|
+
|
|
927
|
+
```typescript
|
|
928
|
+
// Verificar se a configuração é válida
|
|
929
|
+
import { isValidTableConfig } from "@praxisui/core";
|
|
930
|
+
|
|
931
|
+
if (!isValidTableConfig(myConfig)) {
|
|
932
|
+
console.error("Configuração inválida:", myConfig);
|
|
933
|
+
}
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
#### Performance Issues
|
|
937
|
+
|
|
938
|
+
```typescript
|
|
939
|
+
// Habilitar virtualização para grandes datasets
|
|
940
|
+
const config: TableConfig = {
|
|
941
|
+
// ...
|
|
942
|
+
performance: {
|
|
943
|
+
virtualization: {
|
|
944
|
+
enabled: true,
|
|
945
|
+
itemHeight: 48,
|
|
946
|
+
bufferSize: 20,
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
};
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
#### Acessibilidade
|
|
953
|
+
|
|
954
|
+
```typescript
|
|
955
|
+
// Garantir que acessibilidade está habilitada
|
|
956
|
+
const config: TableConfig = {
|
|
957
|
+
// ...
|
|
958
|
+
accessibility: {
|
|
959
|
+
enabled: true,
|
|
960
|
+
announcements: { dataChanges: true, userActions: true, loadingStates: true, liveRegion: "polite" },
|
|
961
|
+
},
|
|
962
|
+
};
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
## 📚 API Reference
|
|
966
|
+
|
|
967
|
+
### Interfaces Principais
|
|
968
|
+
|
|
969
|
+
#### TableConfig
|
|
970
|
+
|
|
971
|
+
Interface principal para configuração da tabela.
|
|
972
|
+
|
|
973
|
+
#### ColumnDefinition
|
|
974
|
+
|
|
975
|
+
Define configuração individual de colunas.
|
|
976
|
+
|
|
977
|
+
#### TableBehaviorConfig
|
|
978
|
+
|
|
979
|
+
Configurações de comportamento (paginação, ordenação, etc.).
|
|
980
|
+
|
|
981
|
+
#### TableAppearanceConfig
|
|
982
|
+
|
|
983
|
+
Configurações de aparência visual.
|
|
984
|
+
|
|
985
|
+
Para documentação completa da API, consulte a [documentação da @praxisui/core](../praxis-core/README.md).
|
|
986
|
+
|
|
987
|
+
## 🤝 Contribuição
|
|
988
|
+
|
|
989
|
+
### Como Contribuir
|
|
990
|
+
|
|
991
|
+
1. Fork o projeto
|
|
992
|
+
2. Crie branch para feature (`git checkout -b feature/nova-funcionalidade`)
|
|
993
|
+
3. Commit mudanças (`git commit -m 'Add: nova funcionalidade'`)
|
|
994
|
+
4. Push para branch (`git push origin feature/nova-funcionalidade`)
|
|
995
|
+
5. Abra Pull Request
|
|
996
|
+
|
|
997
|
+
### Guidelines
|
|
998
|
+
|
|
999
|
+
- Seguir Angular Style Guide
|
|
1000
|
+
- Adicionar testes para novas features
|
|
1001
|
+
- Manter documentação atualizada
|
|
1002
|
+
- Usar TypeScript strict mode
|
|
1003
|
+
|
|
1004
|
+
## 🔍 Exemplo de Integração com PraxisFilter
|
|
1005
|
+
|
|
1006
|
+
O `PraxisFilter` pode ser acoplado à barra de ferramentas da tabela. O exemplo abaixo mostra a busca de pessoas por CPF e status.
|
|
1007
|
+
|
|
1008
|
+
```html
|
|
1009
|
+
<praxis-filter [resourcePath]="'pessoas'" [formId]="'pessoas-filter'" [persistenceKey]="'pessoas-filter-v1'" [quickField]="'cpf'" [alwaysVisibleFields]="['status']" (submit)="onFilter($event)"></praxis-filter> <praxis-table [data]="tableData"></praxis-table>
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
```ts
|
|
1013
|
+
onFilter(dto: any) {
|
|
1014
|
+
this.crud.configure('pessoas', ApiEndpoint.HumanResources);
|
|
1015
|
+
this.crud.filter(dto, this.pageable).subscribe(page => {
|
|
1016
|
+
this.tableData = page.content;
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
### ⚙️ Painel de Configurações do Filtro
|
|
1022
|
+
|
|
1023
|
+
O `PraxisFilter` possui um painel de configurações acessível pelo ícone de
|
|
1024
|
+
engrenagem na barra do filtro ou programaticamente através do método
|
|
1025
|
+
`openSettings()`. Nesse painel é possível ajustar:
|
|
1026
|
+
|
|
1027
|
+
- **quickField** – campo utilizado para a busca rápida
|
|
1028
|
+
- **alwaysVisibleFields** – campos que permanecem sempre visíveis
|
|
1029
|
+
- **placeholder** – texto exibido no campo de busca
|
|
1030
|
+
- **showAdvanced** – define se a seção avançada inicia aberta
|
|
1031
|
+
|
|
1032
|
+
```ts
|
|
1033
|
+
@ViewChild(PraxisFilter) filter!: PraxisFilter;
|
|
1034
|
+
|
|
1035
|
+
abrirConfiguracoes() {
|
|
1036
|
+
this.filter.openSettings();
|
|
1037
|
+
}
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
Ao aplicar ou salvar, as escolhas são validadas contra os metadados
|
|
1041
|
+
disponíveis. O componente exibe uma barra de progresso durante o processo
|
|
1042
|
+
de persistência e mensagens de sucesso ou erro via _snack bar_, garantindo
|
|
1043
|
+
uma experiência consistente.
|
|
1044
|
+
|
|
1045
|
+
### Novos Inputs/Outputs (PraxisFilter)
|
|
1046
|
+
|
|
1047
|
+
- Inputs (efetivos apenas quando `editModeEnabled = true`):
|
|
1048
|
+
- `editModeEnabled: boolean` — habilita o gate de customização para notificações de schema.
|
|
1049
|
+
- `notifyIfOutdated: 'inline' | 'snackbar' | 'both' | 'none' = 'both'` — canal de notificação quando o schema muda.
|
|
1050
|
+
- `snoozeMs: number = 86400000` — tempo de soneca para avisos (ms).
|
|
1051
|
+
- `autoOpenSettingsOnOutdated: boolean = false` — abre Configurações ao detectar schema desatualizado.
|
|
1052
|
+
- Output:
|
|
1053
|
+
- `schemaStatusChange: { outdated: boolean; serverHash?: string; lastVerifiedAt?: string; formId?: string }` — emitido após verificação leve (304/200).
|
|
1054
|
+
|
|
1055
|
+
#### Fallback Global (opcional)
|
|
1056
|
+
|
|
1057
|
+
- Quando os inputs do componente permanecem com os valores padrão e não há overrides locais, o Filter utiliza como último fallback as preferências globais via `GlobalConfigService.getSchemaPrefsGlobal()`.
|
|
1058
|
+
- Cadeia de precedência (inalterada): `@Inputs (widget)` → Prefs do widget → Prefs da página → Prefs globais → Defaults.
|
|
1059
|
+
- O fallback global não é persistido no componente; serve apenas para defaults em memória.
|
|
1060
|
+
|
|
1061
|
+
Notas rápidas (Flow ETag no Filter):
|
|
1062
|
+
- Verificação leve sempre no init (sem baixar o corpo): usa ETag/If-None-Match em `/schemas/filtered` (path=`.../filter`, operation=`post`, schemaType=`request`, `includeInternalSchemas=true`).
|
|
1063
|
+
- 304: atualiza `lastVerifiedAt`; emite `schemaStatusChange(outdated=false)`.
|
|
1064
|
+
- 200: atualiza `serverHash/lastVerifiedAt`; marca `outdated=true` apenas quando em customização; não aplica o schema automaticamente.
|
|
1065
|
+
- O corpo do schema é baixado apenas quando necessário para renderização (ex.: `alwaysVisibleFields` ou ao abrir o painel Avançado).
|
|
1066
|
+
|
|
1067
|
+
## 📊 Roadmap
|
|
1068
|
+
|
|
1069
|
+
### Próximas Versões
|
|
1070
|
+
|
|
1071
|
+
- ✅ Arquitetura unificada (v2.0.0)
|
|
1072
|
+
- 🔄 Enhanced mobile support (v2.1.0)
|
|
1073
|
+
- 📋 Advanced export options (v2.2.0)
|
|
1074
|
+
- 🎨 Theme customization (v2.3.0)
|
|
1075
|
+
|
|
1076
|
+
## 📄 Licença
|
|
1077
|
+
|
|
1078
|
+
Apache-2.0 — consulte [LICENSE](../../LICENSE) para detalhes.
|
|
1079
|
+
|
|
1080
|
+
---
|
|
1081
|
+
|
|
1082
|
+
**Parte do Praxis UI Workspace**
|
|
1083
|
+
**Versão**: 2.0.0 (Unified Architecture)
|
|
1084
|
+
**Compatibilidade**: Angular 18+
|