@oondemand/create-central-oon 0.3.11 → 0.3.14

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.
@@ -0,0 +1,525 @@
1
+ # DETAIL_MODAL_AND_RELATED_GRIDS.md — Modal com Abas e Grids Relacionados
2
+
3
+ Este documento define o contrato desejado para evoluir o `@oondemand/oon-core-front` com modal de detalhe, abas, grids relacionados, edição inline e ações por linha.
4
+
5
+ Status: **especificação para implementação**.
6
+
7
+ ## 1. Motivação
8
+
9
+ Centrais operacionais frequentemente têm uma entidade principal com filhos operacionais.
10
+
11
+ Exemplos:
12
+
13
+ - Projeto -> Itens -> Pagamentos
14
+ - Pedido -> Produtos -> Expedições
15
+ - Contrato -> Parcelas -> Documentos
16
+ - Cliente -> Atividades -> Histórico
17
+ - Migração -> Registros -> Exceções
18
+
19
+ Hoje, esses casos tendem a virar páginas customizadas. O objetivo é abstrair o padrão no Core.
20
+
21
+ ## 2. Resultado esperado
22
+
23
+ O manifesto deve ser capaz de declarar:
24
+
25
+ ```txt
26
+ Grid principal da coleção
27
+ └── ação editar
28
+ └── modal com abas
29
+ ├── Resumo
30
+ ├── Dados Principais
31
+ ├── Itens relacionados editáveis inline
32
+ └── Pagamentos/Histórico somente leitura
33
+ ```
34
+
35
+ ## 3. Extensão proposta do manifesto
36
+
37
+ ### 3.1. Collections com list e detailModal
38
+
39
+ ```json
40
+ {
41
+ "model": "OrcamentoProjeto",
42
+ "mode": "dynamic",
43
+ "path": "/orcamentos-projetos",
44
+ "label": "Orçamentos/Projetos",
45
+ "section": "Operação",
46
+ "list": {
47
+ "filters": [],
48
+ "columns": [],
49
+ "rowActions": []
50
+ },
51
+ "relations": {},
52
+ "detailModal": {
53
+ "enabled": true,
54
+ "titleField": "nome",
55
+ "size": "xl",
56
+ "defaultTab": "resumo",
57
+ "tabs": []
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### 3.2. list.filters
63
+
64
+ Filtros declarativos renderizados acima do grid.
65
+
66
+ ```json
67
+ {
68
+ "field": "tipoRegistro",
69
+ "label": "Tipo",
70
+ "type": "select",
71
+ "options": [
72
+ { "label": "Orçamentos e projetos", "value": "" },
73
+ { "label": "Só orçamentos", "value": "Orçamento" },
74
+ { "label": "Só projetos", "value": "Projeto" }
75
+ ]
76
+ }
77
+ ```
78
+
79
+ Tipos iniciais:
80
+
81
+ - `text`
82
+ - `select`
83
+ - `date`
84
+ - `dateRange`
85
+ - `numberRange`
86
+ - `boolean`
87
+ - `ref`
88
+
89
+ ### 3.3. list.rowActions
90
+
91
+ Ações no grid principal.
92
+
93
+ ```json
94
+ {
95
+ "type": "openDetailModal",
96
+ "label": "Editar",
97
+ "icon": "edit",
98
+ "initialTab": "resumo"
99
+ }
100
+ ```
101
+
102
+ Tipos iniciais:
103
+
104
+ - `openDetailModal`
105
+ - `navigate`
106
+ - `apiAction`
107
+ - `customAction`
108
+
109
+ ### 3.4. relations
110
+
111
+ Relações nomeadas reutilizáveis por abas, filtros e ações.
112
+
113
+ ```json
114
+ {
115
+ "relations": {
116
+ "itens": {
117
+ "model": "OrcamentoItem",
118
+ "foreignKey": "projetoId",
119
+ "parentKey": "_id"
120
+ },
121
+ "pagamentos": {
122
+ "model": "Pagamento",
123
+ "foreignKey": "projetoId",
124
+ "parentKey": "_id"
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## 4. Abas suportadas
131
+
132
+ ### 4.1. summary
133
+
134
+ Cards de resumo.
135
+
136
+ ```json
137
+ {
138
+ "id": "resumo",
139
+ "label": "Resumo",
140
+ "type": "summary",
141
+ "cards": [
142
+ { "label": "Itens", "source": "relatedCount", "relation": "itens" },
143
+ { "label": "Total PARA", "field": "totalParaComImpostos", "format": "currency" }
144
+ ]
145
+ }
146
+ ```
147
+
148
+ Fontes:
149
+
150
+ - `field`
151
+ - `relatedCount`
152
+ - `relatedSum`
153
+ - `relatedAvg`
154
+ - `customMetric`
155
+
156
+ Formatos:
157
+
158
+ - `text`
159
+ - `number`
160
+ - `currency`
161
+ - `percent`
162
+ - `date`
163
+ - `badge`
164
+
165
+ ### 4.2. form
166
+
167
+ Formulário do registro principal.
168
+
169
+ ```json
170
+ {
171
+ "id": "dados",
172
+ "label": "Dados Principais",
173
+ "type": "form",
174
+ "groups": [
175
+ {
176
+ "label": "Identificação",
177
+ "fields": ["tipoRegistro", "codigo", "nome", "status"]
178
+ }
179
+ ]
180
+ }
181
+ ```
182
+
183
+ ### 4.3. relatedGrid
184
+
185
+ Grid de filhos editável ou não.
186
+
187
+ ```json
188
+ {
189
+ "id": "itens",
190
+ "label": "Itens",
191
+ "type": "relatedGrid",
192
+ "relation": "itens",
193
+ "editable": true,
194
+ "editMode": "inline",
195
+ "columns": [],
196
+ "rowActions": []
197
+ }
198
+ ```
199
+
200
+ ### 4.4. readonlyGrid
201
+
202
+ Grid relacionado somente leitura.
203
+
204
+ ```json
205
+ {
206
+ "id": "pagamentos",
207
+ "label": "Pagamentos",
208
+ "type": "readonlyGrid",
209
+ "relation": "pagamentos",
210
+ "columns": ["codigo", "descricao", "statusEsteira", "valorFechamento"]
211
+ }
212
+ ```
213
+
214
+ ### 4.5. customComponent
215
+
216
+ Aba com componente customizado registrado por chave.
217
+
218
+ ```json
219
+ {
220
+ "id": "analise",
221
+ "label": "Análise",
222
+ "type": "customComponent",
223
+ "component": "custom:AnaliseProjeto"
224
+ }
225
+ ```
226
+
227
+ ## 5. Colunas de relatedGrid
228
+
229
+ ```json
230
+ {
231
+ "field": "totalParaComImpostos",
232
+ "label": "Total PARA",
233
+ "editable": true,
234
+ "format": "currency",
235
+ "width": 140
236
+ }
237
+ ```
238
+
239
+ Propriedades:
240
+
241
+ - `field`
242
+ - `label`
243
+ - `editable`
244
+ - `readonly`
245
+ - `format`
246
+ - `renderer`
247
+ - `editor`
248
+ - `width`
249
+ - `hidden`
250
+ - `roles`
251
+ - `required`
252
+
253
+ ## 6. Edição inline
254
+
255
+ O grid editável deve manter alterações em estado local por linha.
256
+
257
+ Requisitos:
258
+
259
+ - célula editável conforme metadata do campo;
260
+ - linha marcada como alterada;
261
+ - botão `Salvar` por linha;
262
+ - botão `Cancelar` por linha;
263
+ - `PUT /:modelPath/:id` usando CRUD genérico;
264
+ - loading por linha;
265
+ - erro por linha;
266
+ - refresh pós-salvamento configurável.
267
+
268
+ ## 7. Row actions
269
+
270
+ ### 7.1. apiAction
271
+
272
+ ```json
273
+ {
274
+ "id": "gerarPagamento",
275
+ "label": "Gerar pagamento",
276
+ "type": "apiAction",
277
+ "method": "POST",
278
+ "endpoint": "/api/ss-eventos/orcamentos-itens/:id/gerar-pagamento",
279
+ "confirm": {
280
+ "title": "Gerar pagamento?",
281
+ "description": "Será criado um ticket financeiro vinculado a este item."
282
+ },
283
+ "disabledWhen": {
284
+ "field": "pagamentoId",
285
+ "exists": true
286
+ },
287
+ "refresh": ["self", "pagamentos", "resumo", "parent"]
288
+ }
289
+ ```
290
+
291
+ ### 7.2. Interpolação de endpoint
292
+
293
+ Suportar:
294
+
295
+ - `:id` -> `_id` da linha;
296
+ - `:parentId` -> `_id` do registro pai;
297
+ - `:fieldName` -> valor do campo na linha;
298
+ - `:parent.fieldName` -> valor do campo no pai.
299
+
300
+ ### 7.3. disabledWhen
301
+
302
+ Operadores mínimos:
303
+
304
+ ```json
305
+ { "field": "pagamentoId", "exists": true }
306
+ { "field": "status", "equals": "Cancelado" }
307
+ { "field": "valor", "gt": 0 }
308
+ { "field": "tipo", "in": ["A", "B"] }
309
+ ```
310
+
311
+ ### 7.4. refresh
312
+
313
+ Alvos:
314
+
315
+ - `self`: grid/aba atual;
316
+ - `parent`: registro principal;
317
+ - nome de aba: `pagamentos`, `resumo` etc.;
318
+ - `all`: todas as queries da modal.
319
+
320
+ ## 8. RBAC
321
+
322
+ Cada nível pode ter `roles` ou `permissions`.
323
+
324
+ ```json
325
+ {
326
+ "id": "gerarPagamento",
327
+ "roles": ["admin", "financeiro"]
328
+ }
329
+ ```
330
+
331
+ A UI deve ocultar/desabilitar conforme permissão, mas a autoridade final continua no backend.
332
+
333
+ ## 9. Componentes Core necessários
334
+
335
+ ### CoreDetailModal
336
+
337
+ Responsável por:
338
+
339
+ - abrir detalhe/criação;
340
+ - buscar registro principal;
341
+ - controlar abas;
342
+ - salvar dados principais;
343
+ - orquestrar refresh;
344
+ - validar RBAC visual;
345
+ - renderizar erros.
346
+
347
+ ### CoreTabbedDetail
348
+
349
+ Renderiza abas configuradas no manifesto.
350
+
351
+ ### CoreRelatedGrid
352
+
353
+ Renderiza grid relacionado pelo `relation`.
354
+
355
+ ### CoreInlineEditableCell
356
+
357
+ Renderiza o editor apropriado por tipo de campo.
358
+
359
+ ### CoreRowAction
360
+
361
+ Executa ações declaradas por linha.
362
+
363
+ ### CoreSummaryCards
364
+
365
+ Renderiza cards de resumo por fields e agregações relacionadas.
366
+
367
+ ## 10. Compatibilidade
368
+
369
+ A implementação deve ser compatível com manifestos existentes.
370
+
371
+ - `collections[]` atual continua funcionando.
372
+ - `detailModal` é opcional.
373
+ - `list` é opcional.
374
+ - Sem `rowActions`, mantém ação padrão do Core.
375
+ - Sem `form.groups`, mantém formulário atual.
376
+
377
+ ## 11. Critérios de aceite
378
+
379
+ - O manifesto consegue declarar a tela de Orçamentos/Projetos da SS Eventos sem página React customizada.
380
+ - A tela principal renderiza filtros, busca, grid e ação editar.
381
+ - A ação editar abre modal com abas.
382
+ - A aba Dados Principais salva o registro pai.
383
+ - A aba Itens carrega apenas filhos do pai selecionado.
384
+ - A aba Itens permite edição inline por linha.
385
+ - A aba Itens executa ação `Gerar pagamento` por linha.
386
+ - Após gerar pagamento, o item mostra badge de pagamento gerado.
387
+ - A aba Pagamentos recarrega automaticamente.
388
+ - A aba Resumo atualiza contadores/totais.
389
+ - RBAC visual respeita roles/permissions declaradas.
390
+ - Sem regressão em coleções simples.
391
+
392
+ ## 12. Exemplo completo SS Eventos
393
+
394
+ ```json
395
+ {
396
+ "model": "OrcamentoProjeto",
397
+ "mode": "dynamic",
398
+ "path": "/orcamentos-projetos",
399
+ "label": "Orçamentos/Projetos",
400
+ "section": "Operação",
401
+ "list": {
402
+ "filters": [
403
+ {
404
+ "field": "tipoRegistro",
405
+ "label": "Tipo",
406
+ "type": "select",
407
+ "options": [
408
+ { "label": "Orçamentos e projetos", "value": "" },
409
+ { "label": "Só orçamentos", "value": "Orçamento" },
410
+ { "label": "Só projetos", "value": "Projeto" }
411
+ ]
412
+ }
413
+ ],
414
+ "columns": ["tipoRegistro", "codigo", "nome", "cliente", "status", "totalItens", "totalParaComImpostos", "lucroTotalEvento"],
415
+ "rowActions": [
416
+ { "type": "openDetailModal", "label": "Editar", "icon": "edit", "initialTab": "resumo" }
417
+ ]
418
+ },
419
+ "relations": {
420
+ "itens": { "model": "OrcamentoItem", "foreignKey": "projetoId", "parentKey": "_id" },
421
+ "pagamentos": { "model": "Pagamento", "foreignKey": "projetoId", "parentKey": "_id" }
422
+ },
423
+ "detailModal": {
424
+ "enabled": true,
425
+ "titleField": "nome",
426
+ "defaultTab": "resumo",
427
+ "tabs": [
428
+ {
429
+ "id": "resumo",
430
+ "label": "Resumo",
431
+ "type": "summary",
432
+ "cards": [
433
+ { "label": "Itens", "source": "relatedCount", "relation": "itens" },
434
+ { "label": "Pagamentos", "source": "relatedCount", "relation": "pagamentos" },
435
+ { "label": "Total PARA", "field": "totalParaComImpostos", "format": "currency" },
436
+ { "label": "Lucro", "field": "lucroTotalEvento", "format": "currency" }
437
+ ]
438
+ },
439
+ {
440
+ "id": "dados",
441
+ "label": "Dados Principais",
442
+ "type": "form",
443
+ "groups": [
444
+ { "label": "Identificação", "fields": ["tipoRegistro", "codigo", "nome", "status", "cliente"] },
445
+ { "label": "Evento", "fields": ["dataEvento", "localEvento", "contato"] },
446
+ { "label": "Totais", "fields": ["totalItens", "totalParaComImpostos", "lucroTotalEvento"] }
447
+ ]
448
+ },
449
+ {
450
+ "id": "itens",
451
+ "label": "Itens",
452
+ "type": "relatedGrid",
453
+ "relation": "itens",
454
+ "editable": true,
455
+ "editMode": "inline",
456
+ "columns": [
457
+ { "field": "linhaPlanilha", "readonly": true },
458
+ { "field": "categoria", "editable": true },
459
+ { "field": "item", "editable": true },
460
+ { "field": "fornecedorRazaoSocial", "editable": true },
461
+ { "field": "status", "editable": true },
462
+ { "field": "totalParaComImpostos", "editable": true, "format": "currency" },
463
+ { "field": "formaPagamento", "editable": true },
464
+ { "field": "pagamentoId", "label": "Pagamento", "readonly": true, "display": "badgeExists" }
465
+ ],
466
+ "rowActions": [
467
+ {
468
+ "id": "gerarPagamento",
469
+ "label": "Gerar pagamento",
470
+ "type": "apiAction",
471
+ "method": "POST",
472
+ "endpoint": "/api/ss-eventos/orcamentos-itens/:id/gerar-pagamento",
473
+ "disabledWhen": { "field": "pagamentoId", "exists": true },
474
+ "refresh": ["self", "pagamentos", "resumo", "parent"]
475
+ }
476
+ ]
477
+ },
478
+ {
479
+ "id": "pagamentos",
480
+ "label": "Pagamentos",
481
+ "type": "readonlyGrid",
482
+ "relation": "pagamentos",
483
+ "columns": ["codigo", "descricao", "fornecedorRazaoSocial", "statusEsteira", "valorFechamento", "formaPagamento", "dataPagamento"]
484
+ }
485
+ ]
486
+ }
487
+ }
488
+ ```
489
+
490
+ ## 11. Status implementado no Core
491
+
492
+ O `@oondemand/oon-core-front` implementa o contrato acima de forma genérica nos componentes `CoreDetailModal`, `CoreTabbedDetail`, `CoreSummaryCards`, `CoreRelatedGrid`, `CoreInlineEditableCell` e `CoreRowAction`.
493
+
494
+ Exemplo completo:
495
+
496
+ ```json
497
+ {
498
+ "model": "OrcamentoProjeto",
499
+ "mode": "dynamic",
500
+ "path": "/orcamentos-projetos",
501
+ "label": "Orçamentos/Projetos",
502
+ "section": "Operação",
503
+ "list": {
504
+ "filters": [{ "field": "tipoRegistro", "label": "Tipo", "type": "select", "options": [{ "label": "Todos", "value": "" }] }],
505
+ "rowActions": [{ "type": "openDetailModal", "label": "Editar", "initialTab": "resumo" }]
506
+ },
507
+ "relations": {
508
+ "itens": { "model": "OrcamentoItem", "foreignKey": "projetoId", "parentKey": "_id" },
509
+ "pagamentos": { "model": "Pagamento", "foreignKey": "projetoId", "parentKey": "_id" }
510
+ },
511
+ "detailModal": {
512
+ "enabled": true,
513
+ "titleField": "nome",
514
+ "defaultTab": "resumo",
515
+ "tabs": [
516
+ { "id": "resumo", "label": "Resumo", "type": "summary", "cards": [{ "label": "Itens", "source": "relatedCount", "relation": "itens" }] },
517
+ { "id": "dados", "label": "Dados Principais", "type": "form", "groups": [{ "label": "Identificação", "fields": ["codigo", "nome", "status"] }] },
518
+ { "id": "itens", "label": "Itens", "type": "relatedGrid", "relation": "itens", "editable": true, "editMode": "inline", "columns": ["item", "status"] },
519
+ { "id": "pagamentos", "label": "Pagamentos", "type": "readonlyGrid", "relation": "pagamentos", "columns": ["codigo", "valorFechamento"] }
520
+ ]
521
+ }
522
+ }
523
+ ```
524
+
525
+ `rowActions` do tipo `apiAction` suportam interpolação de `:id`, `:parentId`, `:fieldName` e `:parent.fieldName`, além de `confirm`, `disabledWhen`, `hiddenWhen` e `refresh` com `self`, `parent`, `all` ou o id de uma aba.