@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/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+