@praxisui/crud 10.0.0-rc.33 → 10.0.0-rc.34

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-18T21:58:32.782Z",
3
+ "generatedAt": "2026-10-19T09:48:25.455Z",
4
4
  "packageName": "@praxisui/crud",
5
- "packageVersion": "10.0.0-rc.33",
5
+ "packageVersion": "10.0.0-rc.34",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 2,
@@ -4624,6 +4624,76 @@
4624
4624
  "valueKind": "object",
4625
4625
  "description": "FormConfig completo (usar catalogo de formulario)."
4626
4626
  },
4627
+ {
4628
+ "path": "kpiBand",
4629
+ "category": "table",
4630
+ "valueKind": "object",
4631
+ "description": "Faixa executiva declarativa de KPIs acima da listagem principal."
4632
+ },
4633
+ {
4634
+ "path": "kpiBand.source",
4635
+ "category": "table",
4636
+ "valueKind": "string",
4637
+ "description": "Path relativo do endpoint de KPIs/stats (ex: /stats, /stats/group-by)."
4638
+ },
4639
+ {
4640
+ "path": "kpiBand.method",
4641
+ "category": "table",
4642
+ "valueKind": "enum",
4643
+ "allowedValues": [
4644
+ "GET",
4645
+ "POST"
4646
+ ],
4647
+ "description": "Metodo HTTP da consulta de KPIs (GET ou POST)."
4648
+ },
4649
+ {
4650
+ "path": "kpiBand.payload",
4651
+ "category": "table",
4652
+ "valueKind": "object",
4653
+ "description": "Payload JSON enviado quando kpiBand.method = POST."
4654
+ },
4655
+ {
4656
+ "path": "kpiBand.cards",
4657
+ "category": "table",
4658
+ "valueKind": "array",
4659
+ "description": "Lista declarativa de cards executivos da faixa de KPIs."
4660
+ },
4661
+ {
4662
+ "path": "kpiBand.cards[].id",
4663
+ "category": "table",
4664
+ "valueKind": "string",
4665
+ "description": "Identificador estavel do card de KPI."
4666
+ },
4667
+ {
4668
+ "path": "kpiBand.cards[].label",
4669
+ "category": "table",
4670
+ "valueKind": "string",
4671
+ "description": "Rotulo executivo do card de KPI."
4672
+ },
4673
+ {
4674
+ "path": "kpiBand.cards[].icon",
4675
+ "category": "table",
4676
+ "valueKind": "string",
4677
+ "description": "Icone Material opcional do card de KPI."
4678
+ },
4679
+ {
4680
+ "path": "kpiBand.cards[].valueExpr",
4681
+ "category": "table",
4682
+ "valueKind": "string",
4683
+ "description": "Dot-path no payload de stats com suporte a pipes declarativos CSP-safe (ex: totalCount | default:0 | number)."
4684
+ },
4685
+ {
4686
+ "path": "kpiBand.cards[].captionExpr",
4687
+ "category": "table",
4688
+ "valueKind": "string",
4689
+ "description": "Subtitulo ou expressao declarativa complementar com suporte a pipes CSP-safe."
4690
+ },
4691
+ {
4692
+ "path": "kpiBand.cards[].clickFilter",
4693
+ "category": "table",
4694
+ "valueKind": "object",
4695
+ "description": "Mapa declarativo de filtro rapido aplicado na tabela ao clicar no card de KPI."
4696
+ },
4627
4697
  {
4628
4698
  "path": "defaults",
4629
4699
  "category": "defaults",
@@ -4968,7 +5038,8 @@
4968
5038
  ],
4969
5039
  "componentCapabilityNotes": [
4970
5040
  "CrudMetadata agrega TableConfig e FormConfig; use os catalogos de tabela e formulario para detalhes.",
4971
- "actions[] herda campos de RowAction e ToolbarAction; combine com openMode/route/formId quando aplicavel."
5041
+ "actions[] herda campos de RowAction e ToolbarAction; combine com openMode/route/formId quando aplicavel.",
5042
+ "kpiBand renderiza cards executivos orientados a endpoint ou projecao declarativa; valueExpr e captionExpr suportam dot-paths e pipes declarativos CSP-safe (default:<fallback>, number, round[:decimals], percent[:decimals], currency[:code], uppercase, lowercase)."
4972
5043
  ],
4973
5044
  "componentContext": {
4974
5045
  "version": "v1",
@@ -8641,9 +8712,9 @@
8641
8712
  {
8642
8713
  "chunkIndex": 11,
8643
8714
  "chunkKind": "capabilities",
8644
- "content": "Component Capability:\n - Path: queryContext.meta.domainCatalog.schemaVersion\n Category: resource\n Kind: string\n Description: Versao do contrato DomainCatalogContextHint usado em runtime e prompts.\n\nComponent Capability:\n - Path: queryContext.meta.domainCatalog.resourceKey\n Category: resource\n Kind: string\n Description: ResourceKey semantico estavel usado para resolver vocabulario/governanca de dominio.\n\nComponent Capability:\n - Path: queryContext.meta.domainCatalog.releaseId\n Category: resource\n Kind: string\n Description: Release opcional do Domain Catalog usada como fonte do contexto.\n\nComponent Capability:\n - Path: queryContext.meta.domainCatalog.query\n Category: resource\n Kind: string\n Description: Probe de campo/conceito usado para buscar itens relevantes, como cpf, salario ou status.\n\nComponent Capability:\n - Path: queryContext.meta.domainCatalog.intent\n Category: resource\n Kind: enum\n Description: Intencao de uso do contexto para orientar LLMs e validadores sem expor dados alem do necessario.\n\nComponent Capability:\n - Path: table\n Category: table\n Kind: object\n Description: TableConfig completo (usar catalogo de tabela).\n\nComponent Capability:\n - Path: form\n Category: form\n Kind: object\n Description: FormConfig completo (usar catalogo de formulario).\n\nComponent Capability:\n - Path: defaults\n Category: defaults\n Kind: object\n Description: Defaults aplicados a acoes e UI.\n\nComponent Capability:\n - Path: defaults.openMode\n Category: defaults\n Kind: enum\n Description: Modo padrao de abertura (route/modal/drawer).\n\nComponent Capability:\n - Path: defaults.modal\n Category: defaults\n Kind: object\n Description: Config de dialog (DialogConfig).\n\nComponent Capability:\n - Path: defaults.back\n Category: navigation\n Kind: object\n Description: Config de retorno (BackConfig).\n\nComponent Capability:\n - Path: defaults.back.strategy\n Category: navigation\n Kind: enum\n Description: Estrategia de retorno.\n\nComponent Capability:\n - Path: defaults.back.returnTo\n Category: navigation\n Kind: string\n Description: Rota de retorno quando strategy=navigate.\n\nComponent Capability:\n - Path: defaults.back.confirmOnDirty\n Category: navigation\n Kind: boolean\n Description: Confirmar ao sair com alteracoes.\n\nComponent Capability:\n - Path: defaults.header\n Category: defaults\n Kind: object\n Description: Config do header do CRUD.\n\nComponent Capability:\n - Path: defaults.header.showBack\n Category: defaults\n Kind: boolean\n Description: Exibir botao voltar.\n\nComponent Capability:\n - Path: defaults.header.backLabel\n Category: defaults\n Kind: string\n Description: Label do botao voltar.\n\nComponent Capability:\n - Path: defaults.header.variant\n Category: defaults\n Kind: enum\n Description: Variante visual do header.\n\nComponent Capability:\n - Path: defaults.header.sticky\n Category: defaults\n Kind: boolean\n Description: Header fixo.\n\nComponent Capability:\n - Path: defaults.header.breadcrumbs\n Category: defaults\n Kind: boolean\n Description: Exibir breadcrumbs.\n\nComponent Capability:\n - Path: defaults.header.divider\n Category: defaults\n Kind: boolean\n Description: Exibir divisor no header.\n\nComponent Capability:\n - Path: actions\n Category: actions\n Kind: array\n Description: Acoes disponiveis no CRUD.\n\nComponent Capability:\n - Path: actions[].id\n Category: actions\n Kind: string\n Description: ID da acao (RowAction/ToolbarAction).\n\nComponent Capability:\n - Path: actions[].label\n Category: actions\n Kind: string\n Description: Label da acao.\n\nComponent Capability:\n - Path: actions[].icon\n Category: actions\n Kind: string\n Description: Icone da acao.\n\nComponent Capability:\n - Path: actions[].color\n Category: actions\n Kind: string\n Description: Cor da acao.\n\nComponent Capability:\n - Path: actions[].disabled\n Category: actions\n Kind: boolean\n Description: Acao desabilitada.\n\nComponent Capability:\n - Path: actions[].action\n Category: actions\n Kind: string\n Description: Nome da acao emitida.\n\nComponent Capability:\n - Path: actions[].tooltip\n Category: actions\n Kind: string\n Description: Tooltip da acao.\n\nComponent Capability:\n - Path: actions[].requiresConfirmation\n Category: actions\n Kind: boolean\n Description: Exige confirmacao.\n\nComponent Capability:\n - Path: actions[].visibleWhen\n Category: actions\n Kind: expression\n Description: Condicao de visibilidade (RowAction/ToolbarAction).\n\nComponent Capability:\n - Path: actions[].separator\n Category: actions\n Kind: boolean\n Description: Separador apos a acao (RowAction).\n\nComponent Capability:\n - Path: actions[].autoDelete\n Category: actions\n Kind: boolean\n Description: Habilita auto delete (RowAction).\n\nComponent Capability:\n - Path: actions[].deleteEndpoint\n Category: actions\n Kind: string\n Description: Endpoint custom para delete (RowAction).\n\nComponent Capability:\n - Path: actions[].type\n Category: actions\n Kind: enum\n Description: Tipo de acao (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].appearance\n Category: actions\n Kind: enum\n Description: Aparencia do botao (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].position\n Category: actions\n Kind: enum\n Description: Posicao da acao (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].order\n Category: actions\n Kind: number\n Description: Ordem de exibicao (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].shortcut\n Category: actions\n Kind: string\n Description: Atalho de teclado (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].children\n Category: actions\n Kind: array\n Description: Sub-acoes (menu).\n\nComponent Capability:\n - Path: actions[].openMode\n Category: actions\n Kind: enum\n Description: Modo de abertura da acao.\n\nComponent Capability:\n - Path: actions[].route\n Category: actions\n Kind: string\n Description: Rota para openMode=route.\n\nComponent Capability:\n - Path: actions[].formId\n Category: actions\n Kind: string\n Description: FormId canonico para modal/drawer; se ausente, o host precisa resolve-lo por override por acao antes da abertura.\n\nComponent Capability:\n - Path: actions[].form.submitLabel\n Category: actions\n Kind: string\n Description: Rótulo de apresentação do submit; não altera schema, método nem endpoint descobertos.\n\nComponent Capability:\n - Path: actions[].params\n Category: actions\n Kind: array\n Description: Mapeamento de parametros.\n\nComponent Capability:\n - Path: actions[].params[].from\n Category: actions\n Kind: string\n Description: Origem do parametro.\n\nComponent Capability:\n - Path: actions[].params[].to\n Category: actions\n Kind: enum\n Description: Destino do parametro.\n\nComponent Capability:\n - Path: actions[].params[].name\n Category: actions\n Kind: string\n Description: Nome do parametro no destino.\n\nComponent Capability:\n - Path: actions[].form.initialValue\n Category: actions\n Kind: object\n Description: Seed fixo do formulario injetado em inputs.initialValue antes da abertura.\n\nComponent Capability:\n - Path: actions[].form.layoutPolicy\n Category: actions\n Kind: object\n Description: Politica schema-driven opt-in encaminhada ao Dynamic Form para materializar detalhe ou command form sem FormConfig.sections local; deve acompanhar schemaUrl compativel, especialmente schemaType=request para comandos.\n\nComponent Capability:\n - Path: actions[].back\n Category: navigation\n Kind: object\n Description: BackConfig por acao.\n\nComponent Capability:\n - Path: actions[].back.strategy\n Category: navigation\n Kind: enum\n Description: Estrategia de retorno da acao (auto, close, navigate).",
8715
+ "content": "Component Capability:\n - Path: queryContext.meta.domainCatalog.schemaVersion\n Category: resource\n Kind: string\n Description: Versao do contrato DomainCatalogContextHint usado em runtime e prompts.\n\nComponent Capability:\n - Path: queryContext.meta.domainCatalog.resourceKey\n Category: resource\n Kind: string\n Description: ResourceKey semantico estavel usado para resolver vocabulario/governanca de dominio.\n\nComponent Capability:\n - Path: queryContext.meta.domainCatalog.releaseId\n Category: resource\n Kind: string\n Description: Release opcional do Domain Catalog usada como fonte do contexto.\n\nComponent Capability:\n - Path: queryContext.meta.domainCatalog.query\n Category: resource\n Kind: string\n Description: Probe de campo/conceito usado para buscar itens relevantes, como cpf, salario ou status.\n\nComponent Capability:\n - Path: queryContext.meta.domainCatalog.intent\n Category: resource\n Kind: enum\n Description: Intencao de uso do contexto para orientar LLMs e validadores sem expor dados alem do necessario.\n\nComponent Capability:\n - Path: table\n Category: table\n Kind: object\n Description: TableConfig completo (usar catalogo de tabela).\n\nComponent Capability:\n - Path: form\n Category: form\n Kind: object\n Description: FormConfig completo (usar catalogo de formulario).\n\nComponent Capability:\n - Path: kpiBand\n Category: table\n Kind: object\n Description: Faixa executiva declarativa de KPIs acima da listagem principal.\n\nComponent Capability:\n - Path: kpiBand.source\n Category: table\n Kind: string\n Description: Path relativo do endpoint de KPIs/stats (ex: /stats, /stats/group-by).\n\nComponent Capability:\n - Path: kpiBand.method\n Category: table\n Kind: enum\n Description: Metodo HTTP da consulta de KPIs (GET ou POST).\n\nComponent Capability:\n - Path: kpiBand.payload\n Category: table\n Kind: object\n Description: Payload JSON enviado quando kpiBand.method = POST.\n\nComponent Capability:\n - Path: kpiBand.cards\n Category: table\n Kind: array\n Description: Lista declarativa de cards executivos da faixa de KPIs.\n\nComponent Capability:\n - Path: kpiBand.cards[].id\n Category: table\n Kind: string\n Description: Identificador estavel do card de KPI.\n\nComponent Capability:\n - Path: kpiBand.cards[].label\n Category: table\n Kind: string\n Description: Rotulo executivo do card de KPI.\n\nComponent Capability:\n - Path: kpiBand.cards[].icon\n Category: table\n Kind: string\n Description: Icone Material opcional do card de KPI.\n\nComponent Capability:\n - Path: kpiBand.cards[].valueExpr\n Category: table\n Kind: string\n Description: Dot-path no payload de stats com suporte a pipes declarativos CSP-safe (ex: totalCount | default:0 | number).\n\nComponent Capability:\n - Path: kpiBand.cards[].captionExpr\n Category: table\n Kind: string\n Description: Subtitulo ou expressao declarativa complementar com suporte a pipes CSP-safe.\n\nComponent Capability:\n - Path: kpiBand.cards[].clickFilter\n Category: table\n Kind: object\n Description: Mapa declarativo de filtro rapido aplicado na tabela ao clicar no card de KPI.\n\nComponent Capability:\n - Path: defaults\n Category: defaults\n Kind: object\n Description: Defaults aplicados a acoes e UI.\n\nComponent Capability:\n - Path: defaults.openMode\n Category: defaults\n Kind: enum\n Description: Modo padrao de abertura (route/modal/drawer).\n\nComponent Capability:\n - Path: defaults.modal\n Category: defaults\n Kind: object\n Description: Config de dialog (DialogConfig).\n\nComponent Capability:\n - Path: defaults.back\n Category: navigation\n Kind: object\n Description: Config de retorno (BackConfig).\n\nComponent Capability:\n - Path: defaults.back.strategy\n Category: navigation\n Kind: enum\n Description: Estrategia de retorno.\n\nComponent Capability:\n - Path: defaults.back.returnTo\n Category: navigation\n Kind: string\n Description: Rota de retorno quando strategy=navigate.\n\nComponent Capability:\n - Path: defaults.back.confirmOnDirty\n Category: navigation\n Kind: boolean\n Description: Confirmar ao sair com alteracoes.\n\nComponent Capability:\n - Path: defaults.header\n Category: defaults\n Kind: object\n Description: Config do header do CRUD.\n\nComponent Capability:\n - Path: defaults.header.showBack\n Category: defaults\n Kind: boolean\n Description: Exibir botao voltar.\n\nComponent Capability:\n - Path: defaults.header.backLabel\n Category: defaults\n Kind: string\n Description: Label do botao voltar.\n\nComponent Capability:\n - Path: defaults.header.variant\n Category: defaults\n Kind: enum\n Description: Variante visual do header.\n\nComponent Capability:\n - Path: defaults.header.sticky\n Category: defaults\n Kind: boolean\n Description: Header fixo.\n\nComponent Capability:\n - Path: defaults.header.breadcrumbs\n Category: defaults\n Kind: boolean\n Description: Exibir breadcrumbs.\n\nComponent Capability:\n - Path: defaults.header.divider\n Category: defaults\n Kind: boolean\n Description: Exibir divisor no header.\n\nComponent Capability:\n - Path: actions\n Category: actions\n Kind: array\n Description: Acoes disponiveis no CRUD.\n\nComponent Capability:\n - Path: actions[].id\n Category: actions\n Kind: string\n Description: ID da acao (RowAction/ToolbarAction).\n\nComponent Capability:\n - Path: actions[].label\n Category: actions\n Kind: string\n Description: Label da acao.\n\nComponent Capability:\n - Path: actions[].icon\n Category: actions\n Kind: string\n Description: Icone da acao.\n\nComponent Capability:\n - Path: actions[].color\n Category: actions\n Kind: string\n Description: Cor da acao.\n\nComponent Capability:\n - Path: actions[].disabled\n Category: actions\n Kind: boolean\n Description: Acao desabilitada.\n\nComponent Capability:\n - Path: actions[].action\n Category: actions\n Kind: string\n Description: Nome da acao emitida.\n\nComponent Capability:\n - Path: actions[].tooltip\n Category: actions\n Kind: string\n Description: Tooltip da acao.\n\nComponent Capability:\n - Path: actions[].requiresConfirmation\n Category: actions\n Kind: boolean\n Description: Exige confirmacao.\n\nComponent Capability:\n - Path: actions[].visibleWhen\n Category: actions\n Kind: expression\n Description: Condicao de visibilidade (RowAction/ToolbarAction).\n\nComponent Capability:\n - Path: actions[].separator\n Category: actions\n Kind: boolean\n Description: Separador apos a acao (RowAction).\n\nComponent Capability:\n - Path: actions[].autoDelete\n Category: actions\n Kind: boolean\n Description: Habilita auto delete (RowAction).\n\nComponent Capability:\n - Path: actions[].deleteEndpoint\n Category: actions\n Kind: string\n Description: Endpoint custom para delete (RowAction).\n\nComponent Capability:\n - Path: actions[].type\n Category: actions\n Kind: enum\n Description: Tipo de acao (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].appearance\n Category: actions\n Kind: enum\n Description: Aparencia do botao (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].position\n Category: actions\n Kind: enum\n Description: Posicao da acao (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].order\n Category: actions\n Kind: number\n Description: Ordem de exibicao (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].shortcut\n Category: actions\n Kind: string\n Description: Atalho de teclado (ToolbarAction).\n\nComponent Capability:\n - Path: actions[].children\n Category: actions\n Kind: array\n Description: Sub-acoes (menu).\n\nComponent Capability:\n - Path: actions[].openMode\n Category: actions\n Kind: enum\n Description: Modo de abertura da acao.\n\nComponent Capability:\n - Path: actions[].route\n Category: actions\n Kind: string\n Description: Rota para openMode=route.",
8645
8716
  "sourcePointer": "projects/praxis-crud/src/lib/ai/crud-ai-capabilities.ts",
8646
- "contentHash": "8ed5bd579131d2ef0a7d4ef52142c617c3f54db3677a06076926e0b43efc5dbb",
8717
+ "contentHash": "4aaac0ea6e968953104a543e5502d81abbabca6d543fd0c6c36a33a008cda907",
8647
8718
  "sourceKind": "component_definition",
8648
8719
  "sourceId": "praxis-crud",
8649
8720
  "corpusVersion": "1.0.0"
@@ -8651,9 +8722,9 @@
8651
8722
  {
8652
8723
  "chunkIndex": 12,
8653
8724
  "chunkKind": "capabilities",
8654
- "content": "Component Capability:\n - Path: actions[].back.returnTo\n Category: navigation\n Kind: string\n Description: Rota ou destino usado quando a estrategia de retorno for navigate.\n\nComponent Capability:\n - Path: actions[].back.confirmOnDirty\n Category: navigation\n Kind: boolean\n Description: Solicita confirmacao antes de sair com alteracoes nao salvas.\n\nComponent Capability:\n - Path: i18n\n Category: i18n\n Kind: object\n Description: Mensagens i18n do CRUD.\n\nComponent Capability:\n - Path: i18n.crudDialog\n Category: i18n\n Kind: object\n Description: Mapa de textos do dialog.\n\nCapability Note:\n - CrudMetadata agrega TableConfig e FormConfig; use os catalogos de tabela e formulario para detalhes.\n\nCapability Note:\n - actions[] herda campos de RowAction e ToolbarAction; combine com openMode/route/formId quando aplicavel.",
8725
+ "content": "Component Capability:\n - Path: actions[].formId\n Category: actions\n Kind: string\n Description: FormId canonico para modal/drawer; se ausente, o host precisa resolve-lo por override por acao antes da abertura.\n\nComponent Capability:\n - Path: actions[].form.submitLabel\n Category: actions\n Kind: string\n Description: Rótulo de apresentação do submit; não altera schema, método nem endpoint descobertos.\n\nComponent Capability:\n - Path: actions[].params\n Category: actions\n Kind: array\n Description: Mapeamento de parametros.\n\nComponent Capability:\n - Path: actions[].params[].from\n Category: actions\n Kind: string\n Description: Origem do parametro.\n\nComponent Capability:\n - Path: actions[].params[].to\n Category: actions\n Kind: enum\n Description: Destino do parametro.\n\nComponent Capability:\n - Path: actions[].params[].name\n Category: actions\n Kind: string\n Description: Nome do parametro no destino.\n\nComponent Capability:\n - Path: actions[].form.initialValue\n Category: actions\n Kind: object\n Description: Seed fixo do formulario injetado em inputs.initialValue antes da abertura.\n\nComponent Capability:\n - Path: actions[].form.layoutPolicy\n Category: actions\n Kind: object\n Description: Politica schema-driven opt-in encaminhada ao Dynamic Form para materializar detalhe ou command form sem FormConfig.sections local; deve acompanhar schemaUrl compativel, especialmente schemaType=request para comandos.\n\nComponent Capability:\n - Path: actions[].back\n Category: navigation\n Kind: object\n Description: BackConfig por acao.\n\nComponent Capability:\n - Path: actions[].back.strategy\n Category: navigation\n Kind: enum\n Description: Estrategia de retorno da acao (auto, close, navigate).\n\nComponent Capability:\n - Path: actions[].back.returnTo\n Category: navigation\n Kind: string\n Description: Rota ou destino usado quando a estrategia de retorno for navigate.\n\nComponent Capability:\n - Path: actions[].back.confirmOnDirty\n Category: navigation\n Kind: boolean\n Description: Solicita confirmacao antes de sair com alteracoes nao salvas.\n\nComponent Capability:\n - Path: i18n\n Category: i18n\n Kind: object\n Description: Mensagens i18n do CRUD.\n\nComponent Capability:\n - Path: i18n.crudDialog\n Category: i18n\n Kind: object\n Description: Mapa de textos do dialog.\n\nCapability Note:\n - CrudMetadata agrega TableConfig e FormConfig; use os catalogos de tabela e formulario para detalhes.\n\nCapability Note:\n - actions[] herda campos de RowAction e ToolbarAction; combine com openMode/route/formId quando aplicavel.\n\nCapability Note:\n - kpiBand renderiza cards executivos orientados a endpoint ou projecao declarativa; valueExpr e captionExpr suportam dot-paths e pipes declarativos CSP-safe (default:<fallback>, number, round[:decimals], percent[:decimals], currency[:code], uppercase, lowercase).",
8655
8726
  "sourcePointer": "projects/praxis-crud/src/lib/ai/crud-ai-capabilities.ts",
8656
- "contentHash": "277fcdaa8b7a971404d062cf365165a123bf854c93ca079b3979c4ce4ba024d4",
8727
+ "contentHash": "066d7fd593f6a0bb7dfe722a70b3dd72f1744ffaddad7ab2d5dd718c791a374a",
8657
8728
  "sourceKind": "component_definition",
8658
8729
  "sourceId": "praxis-crud",
8659
8730
  "corpusVersion": "1.0.0"
@@ -8961,6 +9032,16 @@
8961
9032
  {
8962
9033
  "chunkIndex": 43,
8963
9034
  "chunkKind": "recipe",
9035
+ "content": "{\"componentId\":\"praxis-crud\",\"chunkSection\":\"kpiBand\",\"kpiBand\":{\"enabled\":true,\"source\":\"/api/employees/stats/group-by\",\"method\":\"POST\",\"payload\":{\"field\":\"status\",\"filter\":{},\"metrics\":[{\"operation\":\"COUNT\",\"alias\":\"total\"}]},\"cards\":[{\"id\":\"total-employees\",\"label\":\"Total de Colaboradores\",\"value\":\"0\",\"valueExpr\":\"${totalCount} | number\",\"caption\":\"Quadro geral\",\"icon\":\"groups\",\"variant\":\"primary\",\"filter\":{}},{\"id\":\"active-employees\",\"label\":\"Ativos\",\"value\":\"0\",\"valueExpr\":\"${byKey.ACTIVE} | default:0 | number\",\"caption\":\"Em exercício\",\"icon\":\"verified_user\",\"variant\":\"success\",\"filter\":{\"status\":\"ACTIVE\"}},{\"id\":\"on-leave-employees\",\"label\":\"Afastados\",\"value\":\"0\",\"valueExpr\":\"${byKey.ON_LEAVE} | default:0 | number\",\"captionExpr\":\"${average} | round:1 | default:'0.0'\",\"icon\":\"event_busy\",\"variant\":\"warning\",\"filter\":{\"status\":\"ON_LEAVE\"}}]}}",
9036
+ "sourcePointer": "examples/ai-recipes/praxis-crud.json",
9037
+ "contentHash": "715f8234d24179a380d563e55c2431531ab5d90b4048701076b464ec9d2a8199",
9038
+ "sourceKind": "component_definition",
9039
+ "sourceId": "praxis-crud",
9040
+ "corpusVersion": "1.0.0"
9041
+ },
9042
+ {
9043
+ "chunkIndex": 44,
9044
+ "chunkKind": "recipe",
8964
9045
  "content": "{\"componentId\":\"praxis-crud\",\"chunkSection\":\"defaults\",\"defaults\":{\"openMode\":\"modal\",\"modal\":{\"width\":\"860px\",\"maxWidth\":\"95vw\",\"panelClass\":\"crud-dialog\",\"backdropStyle\":\"blur\",\"autoFocus\":true,\"restoreFocus\":true},\"back\":{\"strategy\":\"auto\",\"returnTo\":\"/employees\",\"confirmOnDirty\":true},\"header\":{\"showBack\":true,\"backLabel\":\"Voltar\",\"variant\":\"outlined\",\"sticky\":true,\"breadcrumbs\":true,\"divider\":true}}}",
8965
9046
  "sourcePointer": "examples/ai-recipes/praxis-crud.json",
8966
9047
  "contentHash": "058ccfdb4c1897b0a4af917f7ff296b4cb5c4fadba4c0c6bcd2ac3d39fe1d989",
@@ -8969,7 +9050,7 @@
8969
9050
  "corpusVersion": "1.0.0"
8970
9051
  },
8971
9052
  {
8972
- "chunkIndex": 44,
9053
+ "chunkIndex": 45,
8973
9054
  "chunkKind": "recipe",
8974
9055
  "content": "{\"componentId\":\"praxis-crud\",\"chunkSection\":\"actions\",\"actions\":[{\"id\":\"create\",\"label\":\"Novo colaborador\",\"icon\":\"add\",\"action\":\"create\",\"openMode\":\"modal\"},{\"id\":\"view\",\"label\":\"Visualizar\",\"icon\":\"visibility\",\"action\":\"view\",\"openMode\":\"modal\"},{\"id\":\"edit\",\"label\":\"Editar\",\"icon\":\"edit\",\"action\":\"edit\",\"openMode\":\"modal\"},{\"id\":\"delete\",\"label\":\"Excluir\",\"icon\":\"delete\",\"action\":\"delete\"},{\"id\":\"edit-route\",\"label\":\"Editar na rota\",\"icon\":\"open_in_new\",\"action\":\"edit-route\",\"openMode\":\"route\",\"route\":\"/employees/:id/edit\",\"params\":[{\"from\":\"id\",\"to\":\"routeParam\",\"name\":\"id\"},{\"from\":\"name\",\"to\":\"query\",\"name\":\"name\"}],\"back\":{\"strategy\":\"navigate\",\"returnTo\":\"/employees\"}}]}",
8975
9056
  "sourcePointer": "examples/ai-recipes/praxis-crud.json",
@@ -8979,7 +9060,7 @@
8979
9060
  "corpusVersion": "1.0.0"
8980
9061
  },
8981
9062
  {
8982
- "chunkIndex": 45,
9063
+ "chunkIndex": 46,
8983
9064
  "chunkKind": "recipe",
8984
9065
  "content": "{\"componentId\":\"praxis-crud\",\"chunkSection\":\"table\",\"table\":{\"meta\":{\"version\":\"2.0.0\",\"id\":\"employees-table\",\"name\":\"Colaboradores\",\"description\":\"Listagem de colaboradores ativos e inativos\",\"idField\":\"id\"},\"columns\":[{\"field\":\"id\",\"header\":\"ID\",\"type\":\"string\",\"width\":\"80px\",\"visible\":false},{\"field\":\"name\",\"header\":\"Nome\",\"type\":\"string\",\"sortable\":true,\"filterable\":true,\"resizable\":true,\"sticky\":\"start\"},{\"field\":\"email\",\"header\":\"Email\",\"type\":\"string\",\"sortable\":true,\"filterable\":true},{\"field\":\"status\",\"header\":\"Status\",\"type\":\"string\",\"sortable\":true,\"filterable\":true,\"valueMapping\":{\"ACTIVE\":\"Ativo\",\"INACTIVE\":\"Inativo\",\"ON_LEAVE\":\"Afastado\"},\"renderer\":{\"type\":\"badge\",\"badge\":{\"textField\":\"statusLabel\",\"color\":\"success\",\"variant\":\"filled\"}},\"conditionalRenderers\":[{\"condition\":{\"==\":[{\"var\":\"status\"},\"INACTIVE\"]},\"renderer\":{\"type\":\"badge\",\"badge\":{\"text\":\"Inativo\",\"color\":\"warn\",\"variant\":\"filled\"}},\"description\":\"Status inativo\",\"enabled\":true},{\"condition\":{\"==\":[{\"var\":\"status\"},\"ON_LEAVE\"]},\"renderer\":{\"type\":\"chip\",\"chip\":{\"text\":\"Afastado\",\"color\":\"accent\",\"variant\":\"outlined\"}},\"description\":\"Status afastado\",\"enabled\":true}]},{\"field\":\"hireDate\",\"header\":\"Admissao\",\"type\":\"date\",\"format\":\"dd/MM/yyyy\",\"sortable\":true,\"filterable\":true},{\"field\":\"salary\",\"header\":\"Salario\",\"type\":\"currency\",\"format\":\"BRL|symbol|2\",\"align\":\"right\",\"sortable\":true,\"filterable\":true}],\"toolbar\":{\"visible\":true,\"position\":\"top\",\"title\":\"Colaboradores\",\"subtitle\":\"Gerencie cadastro, status e remuneracao\",\"layout\":{\"alignment\":\"space-between\",\"showSeparator\":true},\"actions\":[{\"id\":\"create\",\"label\":\"Novo\",\"icon\":\"add\",\"type\":\"button\",\"color\":\"primary\",\"position\":\"end\",\"action\":\"create\"},{\"id\":\"export\",\"label\":\"Exportar\",\"icon\":\"download\",\"type\":\"menu\",\"position\":\"end\",\"action\":\"export\",\"children\":[{\"id\":\"export-csv\",\"label\":\"CSV\",\"icon\":\"table_view\",\"type\":\"button\",\"position\":\"end\",\"action\":\"exportCsv\"},{\"id\":\"export-excel\",\"label\":\"Excel\",\"icon\":\"grid_on\",\"type\":\"button\",\"position\":\"end\",\"action\":\"exportExcel\"}]}],\"filters\":{\"enabled\":true,\"showAdvancedButton\":true},\"settingsMenu\":{\"enabled\":true,\"options\":[{\"id\":\"density\",\"label\":\"Densidade\",\"type\":\"select\",\"options\":[\"compact\",\"comfortable\",\"spacious\"]}]}},\"behavior\":{\"pagination\":{\"enabled\":true,\"pageSize\":20,\"pageSizeOptions\":[10,20,50],\"showFirstLastButtons\":true,\"showPageNumbers\":true,\"showPageInfo\":true,\"position\":\"bottom\",\"style\":\"default\",\"strategy\":\"server\"},\"sorting\":{\"enabled\":true,\"defaultSort\":{\"column\":\"name\",\"direction\":\"asc\"},\"multiSort\":false,\"strategy\":\"server\",\"showSortIndicators\":true,\"indicatorPosition\":\"end\",\"allowClearSort\":true},\"filtering\":{\"enabled\":true,\"columnFilters\":{\"enabled\":true,\"defaultType\":\"text\",\"position\":\"header\"},\"advancedFilters\":{\"enabled\":true,\"queryBuilder\":true,\"savePresets\":true},\"strategy\":\"server\",\"debounceTime\":300},\"selection\":{\"enabled\":true,\"type\":\"multiple\",\"mode\":\"checkbox\",\"allowSelectAll\":true,\"checkboxPosition\":\"start\",\"persistSelection\":false,\"persistOnDataUpdate\":false},\"interaction\":{\"rowClick\":{\"enabled\":true,\"action\":\"view\"},\"rowDoubleClick\":{\"enabled\":true,\"action\":\"edit\"},\"hover\":{\"enabled\":true,\"highlightRow\":true,\"showActionsOnHover\":true},\"keyboard\":{\"enabled\":true}}},\"actions\":{\"row\":{\"enabled\":true,\"position\":\"end\",\"display\":\"icons\",\"trigger\":\"hover\",\"width\":\"140px\",\"actions\":[{\"id\":\"view\",\"label\":\"Ver\",\"icon\":\"visibility\",\"action\":\"view\"},{\"id\":\"edit\",\"label\":\"Editar\",\"icon\":\"edit\",\"action\":\"edit\"},{\"id\":\"delete\",\"label\":\"Excluir\",\"icon\":\"delete\",\"action\":\"delete\",\"requiresConfirmation\":true}],\"header\":{\"label\":\"Acoes\",\"align\":\"center\"}},\"bulk\":{\"enabled\":true,\"position\":\"toolbar\",\"actions\":[{\"id\":\"bulk-delete\",\"label\":\"Excluir selecionados\",\"icon\":\"delete\",\"action\":\"deleteSelected\",\"requiresConfirmation\":true,\"minSelections\":1}],\"floating\":{\"position\":\"bottom-right\",\"hideWhenEmpty\":true}},\"confirmations\":{\"default\":{\"title\":\"Confirmar acao\",\"message\":\"Deseja continuar?\",\"confirmText\":\"Confirmar\",\"cancelText\":\"Cancelar\"},\"specific\":{\"delete\":{\"title\":\"Excluir colaborador\",\"message\":\"Esta acao nao pode ser desfeita.\",\"confirmText\":\"Excluir\",\"cancelText\":\"Cancelar\"}}}},\"export\":{\"enabled\":true,\"formats\":[\"csv\",\"excel\"],\"general\":{\"includeHeaders\":true,\"respectFilters\":true,\"applyFormatting\":true,\"defaultFileName\":\"colaboradores\",\"includeColumns\":[\"id\",\"name\",\"email\",\"status\",\"hireDate\",\"salary\"]},\"excel\":{\"sheetName\":\"Colaboradores\",\"includeFormulas\":false,\"freezeHeaders\":true,\"autoFitColumns\":true},\"csv\":{\"delimiter\":\";\",\"escapeChar\":\"\\\"\",\"encoding\":\"utf-8\",\"includeBOM\":true}},\"messages\":{\"states\":{\"loading\":\"Carregando colaboradores...\",\"empty\":\"Nenhum colaborador encontrado.\",\"error\":\"Erro ao carregar dados.\",\"noResults\":\"Sem resultados para o filtro atual.\",\"loadingMore\":\"Carregando mais registros...\"},\"actions\":{\"confirmations\":{\"delete\":\"Excluir este colaborador?\",\"deleteMultiple\":\"Excluir os colaboradores selecionados?\",\"save\":\"Salvar alteracoes?\",\"cancel\":\"Cancelar edicao?\",\"export\":\"Exportar dados agora?\"},\"success\":{\"save\":\"Colaborador salvo com sucesso.\",\"delete\":\"Colaborador excluido.\",\"export\":\"Exportacao concluida.\"},\"errors\":{\"save\":\"Falha ao salvar.\",\"delete\":\"Falha ao excluir.\",\"export\":\"Falha ao exportar.\"}}},\"localization\":{\"locale\":\"pt-BR\",\"direction\":\"ltr\",\"dateTime\":{\"dateFormat\":\"dd/MM/yyyy\",\"timeFormat\":\"HH:mm\",\"dateTimeFormat\":\"dd/MM/yyyy HH:mm\",\"firstDayOfWeek\":1}},\"appearance\":{\"density\":\"comfortable\",\"borders\":{\"showRowBorders\":true,\"showColumnBorders\":false,\"showOuterBorder\":true,\"style\":\"solid\",\"width\":1,\"color\":\"#e0e0e0\"}}}}",
8985
9066
  "sourcePointer": "examples/ai-recipes/praxis-crud.json",
@@ -8989,7 +9070,7 @@
8989
9070
  "corpusVersion": "1.0.0"
8990
9071
  },
8991
9072
  {
8992
- "chunkIndex": 46,
9073
+ "chunkIndex": 47,
8993
9074
  "chunkKind": "recipe",
8994
9075
  "content": "{\"componentId\":\"praxis-crud\",\"chunkSection\":\"form\",\"form\":{\"metadata\":{\"version\":\"2.0\",\"source\":\"local\",\"schemaId\":\"path:/api/employees#request\",\"schemaContext\":{\"path\":\"/api/employees\",\"operation\":\"POST\",\"schemaType\":\"request\",\"internal\":false,\"tenant\":\"default\",\"locale\":\"pt-BR\"}},\"api\":{\"saveEndpoint\":\"/api/employees\",\"loadEndpoint\":\"/api/employees/${id}\",\"saveMethod\":\"POST\",\"timeout\":15000,\"headers\":{\"X-Module\":\"HR\"},\"idField\":\"id\",\"beforeSave\":\"sanitizeEmployee\",\"afterLoad\":\"mapEmployee\"},\"behavior\":{\"confirmOnUnsavedChanges\":true,\"trackHistory\":true,\"focusFirstError\":true,\"scrollToErrors\":true,\"clearAfterSave\":false,\"reactiveValidation\":true,\"reactiveValidationDebounceMs\":200},\"messages\":{\"createRegistrySuccess\":\"Colaborador criado com sucesso.\",\"updateRegistrySuccess\":\"Colaborador atualizado com sucesso.\",\"createRegistryError\":\"Falha ao criar colaborador.\",\"updateRegistryError\":\"Falha ao atualizar colaborador.\",\"confirmations\":{\"submit\":\"Deseja salvar as alteracoes?\",\"cancel\":\"Descartar alteracoes?\",\"reset\":\"Limpar o formulario?\"},\"customActions\":{\"archive\":{\"confirmation\":\"Arquivar este colaborador?\"}},\"loading\":{\"submit\":\"Salvando...\",\"cancel\":\"Cancelando...\"}},\"hints\":{\"dataModes\":{\"create\":\"Preencha os dados para criar um novo colaborador.\",\"edit\":\"Atualize os dados e salve as alteracoes.\",\"view\":\"Visualize os dados do colaborador.\"},\"uiModes\":{\"presentation\":\"Modo apresentacao mostra somente leitura.\",\"readonly\":\"Modo leitura bloqueia edicao.\",\"disabled\":\"Campos desabilitados nao permitem interacao.\",\"visible\":\"Controle de visibilidade sem destruir controles.\"}},\"hooks\":{\"beforeSubmit\":[{\"id\":\"sanitize-employee\",\"priority\":100,\"args\":{\"trimStrings\":true}}],\"afterSubmit\":[{\"id\":\"notify-success\",\"args\":{\"messageKey\":\"employeeSaved\"}}],\"onError\":[{\"id\":\"notify-error\",\"args\":{\"messageKey\":\"employeeSaveError\"}}]},\"fieldMetadata\":[{\"name\":\"id\",\"label\":\"ID\",\"controlType\":\"numericTextBox\",\"dataType\":\"number\",\"hidden\":true,\"readOnly\":true,\"description\":\"Campo ID oculto, gerenciado pelo backend.\"},{\"name\":\"name\",\"label\":\"Nome completo\",\"controlType\":\"input\",\"dataType\":\"text\",\"placeholder\":\"Ex.: Ana Souza\",\"prefixIcon\":\"person\",\"validators\":{\"required\":true,\"minLength\":3}},{\"name\":\"email\",\"label\":\"Email\",\"controlType\":\"email\",\"dataType\":\"email\",\"placeholder\":\"nome@empresa.com\",\"validators\":{\"required\":true,\"email\":true}},{\"name\":\"phone\",\"label\":\"Telefone\",\"controlType\":\"phone\",\"placeholder\":\"(11) 99999-0000\",\"mask\":\"(99) 99999-9999\"},{\"name\":\"status\",\"label\":\"Status\",\"controlType\":\"select\",\"options\":[{\"text\":\"Ativo\",\"value\":\"ACTIVE\"},{\"text\":\"Inativo\",\"value\":\"INACTIVE\"},{\"text\":\"Afastado\",\"value\":\"ON_LEAVE\"}],\"defaultValue\":\"ACTIVE\",\"materialDesign\":{\"appearance\":\"outline\"}},{\"name\":\"hireDate\",\"label\":\"Data de admissao\",\"controlType\":\"date\",\"dataType\":\"date\"},{\"name\":\"terminationDate\",\"label\":\"Data de desligamento\",\"controlType\":\"date\",\"conditionalDisplay\":{\"==\":[{\"var\":\"status\"},\"INACTIVE\"]},\"conditionalRequired\":{\"==\":[{\"var\":\"status\"},\"INACTIVE\"]}},{\"name\":\"department\",\"label\":\"Departamento\",\"controlType\":\"select\",\"endpoint\":\"/api/departments\",\"valueField\":\"id\",\"displayField\":\"name\"},{\"name\":\"role\",\"label\":\"Cargo\",\"controlType\":\"select\",\"options\":[{\"text\":\"Analista\",\"value\":\"ANALYST\"},{\"text\":\"Coordenador\",\"value\":\"COORD\"},{\"text\":\"Gerente\",\"value\":\"MANAGER\"}]},{\"name\":\"managerId\",\"label\":\"Gestor\",\"controlType\":\"autoComplete\",\"endpoint\":\"/api/employees?role=manager\",\"valueField\":\"id\",\"displayField\":\"name\",\"filterField\":\"name\"},{\"name\":\"salary\",\"label\":\"Salario\",\"controlType\":\"currency\",\"dataType\":\"number\",\"format\":\"BRL|symbol|2\",\"validators\":{\"min\":0}},{\"name\":\"workMode\",\"label\":\"Regime\",\"controlType\":\"radio\",\"options\":[{\"text\":\"CLT\",\"value\":\"CLT\"},{\"text\":\"PJ\",\"value\":\"PJ\"},{\"text\":\"Estagio\",\"value\":\"INTERN\"}],\"defaultValue\":\"CLT\"},{\"name\":\"notes\",\"label\":\"Observacoes\",\"controlType\":\"textarea\",\"validators\":{\"maxLength\":500},\"hint\":\"Informacoes adicionais relevantes ao cadastro.\"},{\"name\":\"isActive\",\"label\":\"Ativo\",\"controlType\":\"toggle\",\"defaultValue\":true},{\"name\":\"avatar\",\"label\":\"Foto\",\"controlType\":\"upload\",\"accept\":\".png,.jpg,.jpeg\",\"multiple\":false,\"materialDesign\":{\"appearance\":\"outline\"}}],\"sections\":[{\"id\":\"basic\",\"title\":\"Dados basicos\",\"description\":\"Informacoes pessoais e status.\",\"icon\":\"person\",\"collapsible\":true,\"collapsed\":false,\"titleStyle\":\"titleMedium\",\"descriptionStyle\":\"bodyMedium\",\"rows\":[{\"id\":\"row-basic-1\",\"columns\":[{\"id\":\"col-basic-1\",\"fields\":[\"name\",\"email\"],\"span\":{\"md\":6,\"lg\":6},\"padding\":8},{\"id\":\"col-basic-2\",\"fields\":[\"status\",\"hireDate\"],\"span\":{\"md\":6,\"lg\":6}}]},{\"id\":\"row-basic-2\",\"columns\":[{\"id\":\"col-basic-3\",\"fields\":[\"phone\",\"managerId\"],\"span\":{\"md\":6,\"lg\":6}},{\"id\":\"col-basic-4\",\"fields\":[\"department\",\"role\"],\"span\":{\"md\":6,\"lg\":6}}]}]},{\"id\":\"employment\",\"title\":\"Contrato\",\"icon\":\"badge\",\"rows\":[{\"id\":\"row-employment-1\",\"columns\":[{\"id\":\"col-employment-1\",\"fields\":[\"salary\",\"workMode\"],\"span\":{\"md\":6,\"lg\":6}},{\"id\":\"col-employment-2\",\"fields\":[\"terminationDate\",\"notes\"],\"span\":{\"md\":6,\"lg\":6}}]}]},{\"id\":\"attachments\",\"title\":\"Arquivos\",\"collapsible\":true,\"collapsed\":true,\"rows\":[{\"id\":\"row-attachments-1\",\"columns\":[{\"id\":\"col-attachments-1\",\"fields\":[\"avatar\"],\"span\":{\"md\":12,\"lg\":12},\"align\":\"center\"}]}]}],\"actions\":{\"submit\":{\"visible\":true,\"label\":\"Salvar\",\"color\":\"primary\",\"variant\":\"raised\",\"type\":\"submit\",\"shortcut\":\"Ctrl+S\"},\"cancel\":{\"visible\":true,\"label\":\"Cancelar\",\"type\":\"button\"},\"reset\":{\"visible\":true,\"label\":\"Limpar\",\"type\":\"reset\"},\"custom\":[{\"id\":\"archive\",\"visible\":true,\"label\":\"Arquivar\",\"icon\":\"archive\",\"color\":\"warn\",\"variant\":\"stroked\",\"action\":\"archive\"}],\"position\":\"right\",\"orientation\":\"horizontal\",\"spacing\":\"normal\",\"sticky\":true,\"divider\":true,\"placement\":\"afterSections\",\"mobile\":{\"position\":\"center\",\"orientation\":\"vertical\",\"collapseToMenu\":true}},\"formRules\":[{\"id\":\"hide-salary-inactive\",\"name\":\"Ocultar salario para inativos\",\"targetType\":\"field\",\"targets\":[\"salary\"],\"effect\":{\"condition\":{\"==\":[{\"var\":\"status\"},\"INACTIVE\"]},\"properties\":{\"visible\":false}}},{\"id\":\"require-termination-date\",\"name\":\"Exigir data de desligamento\",\"targetType\":\"field\",\"targets\":[\"terminationDate\"],\"effect\":{\"condition\":{\"==\":[{\"var\":\"status\"},\"INACTIVE\"]},\"properties\":{\"required\":true,\"visible\":true}}}]}}",
8995
9076
  "sourcePointer": "examples/ai-recipes/praxis-crud.json",
@@ -8999,7 +9080,7 @@
8999
9080
  "corpusVersion": "1.0.0"
9000
9081
  },
9001
9082
  {
9002
- "chunkIndex": 47,
9083
+ "chunkIndex": 48,
9003
9084
  "chunkKind": "recipe",
9004
9085
  "content": "{\"componentId\":\"praxis-crud\",\"chunkSection\":\"i18n\",\"i18n\":{\"crudDialog\":{\"createTitle\":\"Novo colaborador\",\"editTitle\":\"Editar colaborador\",\"viewTitle\":\"Detalhes do colaborador\",\"deleteTitle\":\"Confirmar exclusao\",\"deleteConfirm\":\"Tem certeza que deseja excluir este colaborador?\"}}}",
9005
9086
  "sourcePointer": "examples/ai-recipes/praxis-crud.json",
@@ -9009,7 +9090,7 @@
9009
9090
  "corpusVersion": "1.0.0"
9010
9091
  },
9011
9092
  {
9012
- "chunkIndex": 48,
9093
+ "chunkIndex": 49,
9013
9094
  "chunkKind": "recipe",
9014
9095
  "content": "{\"_comment\":\"Variant focused on CRUD table configuration.\",\"templateMeta\":{\"variantId\":\"table\",\"variantOf\":\"praxis-crud\",\"registryKey\":\"praxis-crud\",\"tags\":[\"table\",\"crud\"],\"useWhen\":\"user asks to change table columns or behavior inside CRUD\",\"examplePrompts\":[\"adjust the columns shown in the crud table\",\"enable selection in the crud table\"]},\"resource\":{\"path\":\"/api/employees\",\"idField\":\"id\",\"endpointKey\":\"employees\"},\"table\":{\"meta\":{\"version\":\"2.0.0\",\"id\":\"employees-table\",\"name\":\"Colaboradores\",\"description\":\"Listagem de colaboradores ativos e inativos\",\"idField\":\"id\"},\"columns\":[{\"field\":\"id\",\"header\":\"ID\",\"type\":\"string\",\"width\":\"80px\",\"visible\":false},{\"field\":\"name\",\"header\":\"Nome\",\"type\":\"string\",\"sortable\":true,\"filterable\":true,\"resizable\":true,\"sticky\":\"start\"},{\"field\":\"email\",\"header\":\"Email\",\"type\":\"string\",\"sortable\":true,\"filterable\":true},{\"field\":\"status\",\"header\":\"Status\",\"type\":\"string\",\"sortable\":true,\"filterable\":true,\"valueMapping\":{\"ACTIVE\":\"Ativo\",\"INACTIVE\":\"Inativo\",\"ON_LEAVE\":\"Afastado\"},\"renderer\":{\"type\":\"badge\",\"badge\":{\"textField\":\"statusLabel\",\"color\":\"success\",\"variant\":\"filled\"}},\"conditionalRenderers\":[{\"condition\":{\"==\":[{\"var\":\"status\"},\"INACTIVE\"]},\"renderer\":{\"type\":\"badge\",\"badge\":{\"text\":\"Inativo\",\"color\":\"warn\",\"variant\":\"filled\"}},\"description\":\"Status inativo\",\"enabled\":true},{\"condition\":{\"==\":[{\"var\":\"status\"},\"ON_LEAVE\"]},\"renderer\":{\"type\":\"chip\",\"chip\":{\"text\":\"Afastado\",\"color\":\"accent\",\"variant\":\"outlined\"}},\"description\":\"Status afastado\",\"enabled\":true}]},{\"field\":\"hireDate\",\"header\":\"Admissao\",\"type\":\"date\",\"format\":\"dd/MM/yyyy\",\"sortable\":true,\"filterable\":true},{\"field\":\"salary\",\"header\":\"Salario\",\"type\":\"currency\",\"format\":\"BRL|symbol|2\",\"align\":\"right\",\"sortable\":true,\"filterable\":true}],\"toolbar\":{\"visible\":true,\"position\":\"top\",\"title\":\"Colaboradores\",\"subtitle\":\"Gerencie cadastro, status e remuneracao\",\"layout\":{\"alignment\":\"space-between\",\"showSeparator\":true},\"actions\":[{\"id\":\"create\",\"label\":\"Novo\",\"icon\":\"add\",\"type\":\"button\",\"color\":\"primary\",\"position\":\"end\",\"action\":\"create\"},{\"id\":\"export\",\"label\":\"Exportar\",\"icon\":\"download\",\"type\":\"menu\",\"position\":\"end\",\"action\":\"export\",\"children\":[{\"id\":\"export-csv\",\"label\":\"CSV\",\"icon\":\"table_view\",\"type\":\"button\",\"position\":\"end\",\"action\":\"exportCsv\"},{\"id\":\"export-excel\",\"label\":\"Excel\",\"icon\":\"grid_on\",\"type\":\"button\",\"position\":\"end\",\"action\":\"exportExcel\"}]}],\"filters\":{\"enabled\":true,\"showAdvancedButton\":true},\"settingsMenu\":{\"enabled\":true,\"options\":[{\"id\":\"density\",\"label\":\"Densidade\",\"type\":\"select\",\"options\":[\"compact\",\"comfortable\",\"spacious\"]}]}},\"behavior\":{\"pagination\":{\"enabled\":true,\"pageSize\":20,\"pageSizeOptions\":[10,20,50],\"showFirstLastButtons\":true,\"showPageNumbers\":true,\"showPageInfo\":true,\"position\":\"bottom\",\"style\":\"default\",\"strategy\":\"server\"},\"sorting\":{\"enabled\":true,\"defaultSort\":{\"column\":\"name\",\"direction\":\"asc\"},\"multiSort\":false,\"strategy\":\"server\",\"showSortIndicators\":true,\"indicatorPosition\":\"end\",\"allowClearSort\":true},\"filtering\":{\"enabled\":true,\"columnFilters\":{\"enabled\":true,\"defaultType\":\"text\",\"position\":\"header\"},\"advancedFilters\":{\"enabled\":true,\"queryBuilder\":true,\"savePresets\":true},\"strategy\":\"server\",\"debounceTime\":300},\"selection\":{\"enabled\":true,\"type\":\"multiple\",\"mode\":\"checkbox\",\"allowSelectAll\":true,\"checkboxPosition\":\"start\",\"persistSelection\":false,\"persistOnDataUpdate\":false},\"interaction\":{\"rowClick\":{\"enabled\":true,\"action\":\"view\"},\"rowDoubleClick\":{\"enabled\":true,\"action\":\"edit\"},\"hover\":{\"enabled\":true,\"highlightRow\":true,\"showActionsOnHover\":true},\"keyboard\":{\"enabled\":true}}},\"actions\":{\"row\":{\"enabled\":true,\"position\":\"end\",\"display\":\"icons\",\"trigger\":\"hover\",\"width\":\"140px\",\"actions\":[{\"id\":\"view\",\"label\":\"Ver\",\"icon\":\"visibility\",\"action\":\"view\"},{\"id\":\"edit\",\"label\":\"Editar\",\"icon\":\"edit\",\"action\":\"edit\"},{\"id\":\"delete\",\"label\":\"Excluir\",\"icon\":\"delete\",\"action\":\"delete\",\"requiresConfirmation\":true}],\"header\":{\"label\":\"Acoes\",\"align\":\"center\"}},\"bulk\":{\"enabled\":true,\"position\":\"toolbar\",\"actions\":[{\"id\":\"bulk-delete\",\"label\":\"Excluir selecionados\",\"icon\":\"delete\",\"action\":\"deleteSelected\",\"requiresConfirmation\":true,\"minSelections\":1}],\"floating\":{\"position\":\"bottom-right\",\"hideWhenEmpty\":true}},\"confirmations\":{\"default\":{\"title\":\"Confirmar acao\",\"message\":\"Deseja continuar?\",\"confirmText\":\"Confirmar\",\"cancelText\":\"Cancelar\"},\"specific\":{\"delete\":{\"title\":\"Excluir colaborador\",\"message\":\"Esta acao nao pode ser desfeita.\",\"confirmText\":\"Excluir\",\"cancelText\":\"Cancelar\"}}}},\"export\":{\"enabled\":true,\"formats\":[\"csv\",\"excel\"],\"general\":{\"includeHeaders\":true,\"respectFilters\":true,\"applyFormatting\":true,\"defaultFileName\":\"colaboradores\",\"includeColumns\":[\"id\",\"name\",\"email\",\"status\",\"hireDate\",\"salary\"]},\"excel\":{\"sheetName\":\"Colaboradores\",\"includeFormulas\":false,\"freezeHeaders\":true,\"autoFitColumns\":true},\"csv\":{\"delimiter\":\";\",\"escapeChar\":\"\\\"\",\"encoding\":\"utf-8\",\"includeBOM\":true}},\"messages\":{\"states\":{\"loading\":\"Carregando colaboradores...\",\"empty\":\"Nenhum colaborador encontrado.\",\"error\":\"Erro ao carregar dados.\",\"noResults\":\"Sem resultados para o filtro atual.\",\"loadingMore\":\"Carregando mais registros...\"},\"actions\":{\"confirmations\":{\"delete\":\"Excluir este colaborador?\",\"deleteMultiple\":\"Excluir os colaboradores selecionados?\",\"save\":\"Salvar alteracoes?\",\"cancel\":\"Cancelar edicao?\",\"export\":\"Exportar dados agora?\"},\"success\":{\"save\":\"Colaborador salvo com sucesso.\",\"delete\":\"Colaborador excluido.\",\"export\":\"Exportacao concluida.\"},\"errors\":{\"save\":\"Falha ao salvar.\",\"delete\":\"Falha ao excluir.\",\"export\":\"Falha ao exportar.\"}}},\"localization\":{\"locale\":\"pt-BR\",\"direction\":\"ltr\",\"dateTime\":{\"dateFormat\":\"dd/MM/yyyy\",\"timeFormat\":\"HH:mm\",\"dateTimeFormat\":\"dd/MM/yyyy HH:mm\",\"firstDayOfWeek\":1}},\"appearance\":{\"density\":\"comfortable\",\"borders\":{\"showRowBorders\":true,\"showColumnBorders\":false,\"showOuterBorder\":true,\"style\":\"solid\",\"width\":1,\"color\":\"#e0e0e0\"}}}}",
9015
9096
  "sourcePointer": "examples/ai-recipes/praxis-crud.table.json",
@@ -2973,7 +2973,13 @@ class PraxisKpiBandComponent {
2973
2973
  return;
2974
2974
  }
2975
2975
  if (changes['config'] || changes['dataContext'] || changes['activeFilter'] || changes['isInteractiveHost']) {
2976
- if (changes['config'] && this.config?.source !== changes['config'].previousValue?.source) {
2976
+ const prevConfig = changes['config']?.previousValue;
2977
+ const nextConfig = this.config;
2978
+ const shouldRefetch = !!changes['config'] &&
2979
+ (nextConfig?.source !== prevConfig?.source ||
2980
+ nextConfig?.method !== prevConfig?.method ||
2981
+ JSON.stringify(nextConfig?.payload ?? null) !== JSON.stringify(prevConfig?.payload ?? null));
2982
+ if (shouldRefetch) {
2977
2983
  this.refresh();
2978
2984
  }
2979
2985
  else {
@@ -3019,7 +3025,7 @@ class PraxisKpiBandComponent {
3019
3025
  const effectiveUrl = this.resolveSourceUrl(source);
3020
3026
  const method = this.config?.method || 'GET';
3021
3027
  const request$ = method.toUpperCase() === 'POST'
3022
- ? this.http.post(effectiveUrl, this.config?.payload || {})
3028
+ ? this.http.post(effectiveUrl, this.normalizeSourceRequestPayload(effectiveUrl, this.config?.payload))
3023
3029
  : this.http.get(effectiveUrl);
3024
3030
  this.fetchSub = request$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
3025
3031
  next: (res) => {
@@ -3038,6 +3044,54 @@ class PraxisKpiBandComponent {
3038
3044
  },
3039
3045
  });
3040
3046
  }
3047
+ normalizeSourceRequestPayload(url, rawPayload) {
3048
+ const body = rawPayload && typeof rawPayload === 'object' && !Array.isArray(rawPayload)
3049
+ ? { ...rawPayload }
3050
+ : {};
3051
+ if (!/\/stats\/(?:group-by|timeseries|distribution|comparison)(?:$|\?|\/)/i.test(url)) {
3052
+ return body;
3053
+ }
3054
+ if (/\/stats\/group-by(?:$|\?|\/)/i.test(url) &&
3055
+ (!body['field'] || typeof body['field'] !== 'string' || !body['field'].trim()) &&
3056
+ typeof body['groupBy'] === 'string' &&
3057
+ body['groupBy'].trim()) {
3058
+ body['field'] = body['groupBy'].trim();
3059
+ }
3060
+ if (!body['filter'] || typeof body['filter'] !== 'object' || Array.isArray(body['filter'])) {
3061
+ body['filter'] = {};
3062
+ }
3063
+ const sanitizeMetric = (m) => {
3064
+ const copy = { ...m };
3065
+ const rawOp = typeof copy['operation'] === 'string' && copy['operation'].trim()
3066
+ ? copy['operation'].trim()
3067
+ : typeof copy['op'] === 'string' && copy['op'].trim()
3068
+ ? copy['op'].trim()
3069
+ : 'COUNT';
3070
+ const operation = rawOp.toUpperCase();
3071
+ copy['operation'] = operation;
3072
+ if (operation === 'COUNT') {
3073
+ delete copy['field'];
3074
+ if (typeof copy['alias'] !== 'string' || !copy['alias'].trim()) {
3075
+ copy['alias'] = 'total';
3076
+ }
3077
+ }
3078
+ return copy;
3079
+ };
3080
+ if (body['metric'] && typeof body['metric'] === 'object' && !Array.isArray(body['metric'])) {
3081
+ body['metric'] = sanitizeMetric(body['metric']);
3082
+ }
3083
+ if (Array.isArray(body['metrics']) && body['metrics'].length > 0) {
3084
+ body['metrics'] = body['metrics']
3085
+ .filter((item) => !!item && typeof item === 'object' && !Array.isArray(item))
3086
+ .map(sanitizeMetric);
3087
+ }
3088
+ const hasMetric = !!body['metric'];
3089
+ const hasMetrics = Array.isArray(body['metrics']) && body['metrics'].length > 0;
3090
+ if (!hasMetric && !hasMetrics) {
3091
+ body['metrics'] = [{ operation: 'COUNT', alias: 'total' }];
3092
+ }
3093
+ return body;
3094
+ }
3041
3095
  normalizeSourcePayload(payload) {
3042
3096
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
3043
3097
  return payload;
@@ -3049,7 +3103,18 @@ class PraxisKpiBandComponent {
3049
3103
  for (const bucket of payload['buckets']) {
3050
3104
  if (!bucket || typeof bucket !== 'object')
3051
3105
  continue;
3052
- const count = Number(bucket.values?.['total'] ?? bucket.value ?? bucket.count ?? 0);
3106
+ const valuesObj = bucket.values && typeof bucket.values === 'object' && !Array.isArray(bucket.values)
3107
+ ? bucket.values
3108
+ : null;
3109
+ const firstValuesMetric = valuesObj
3110
+ ? Object.values(valuesObj).find((v) => typeof v === 'number' && Number.isFinite(v))
3111
+ : undefined;
3112
+ const count = Number(valuesObj?.['total'] ??
3113
+ valuesObj?.['count'] ??
3114
+ firstValuesMetric ??
3115
+ bucket.value ??
3116
+ bucket.count ??
3117
+ 0);
3053
3118
  if (!Number.isFinite(count) || count < 0)
3054
3119
  continue;
3055
3120
  totalCount += count;
@@ -3166,6 +3231,11 @@ class PraxisKpiBandComponent {
3166
3231
  const inner = (singleTokenMatch[1] ?? singleTokenMatch[2] ?? '').trim();
3167
3232
  return this.resolveKpiPipeChain(inner, context);
3168
3233
  }
3234
+ const outsideTokens = trimmed.replace(/\$\{[^}]+\}|\{\{\s*[^}]+?\s*\}\}/g, '');
3235
+ const hasTopLevelPipe = /(?<!\|)\|(?!\|)/.test(outsideTokens);
3236
+ if (hasTopLevelPipe) {
3237
+ return this.resolveKpiPipeChain(trimmed, context);
3238
+ }
3169
3239
  if (this.hasTemplateTokens(trimmed)) {
3170
3240
  let resolvedAny = false;
3171
3241
  const interpolated = trimmed
@@ -3204,10 +3274,29 @@ class PraxisKpiBandComponent {
3204
3274
  if (!segments.length) {
3205
3275
  return undefined;
3206
3276
  }
3207
- const head = segments[0];
3277
+ const rawHead = segments[0];
3278
+ const unwrappedSingle = /^(?:\$\{([^}]+)\}|\{\{\s*([^}]+?)\s*\}\})$/.exec(rawHead);
3279
+ const head = unwrappedSingle
3280
+ ? (unwrappedSingle[1] ?? unwrappedSingle[2] ?? '').trim()
3281
+ : rawHead;
3208
3282
  let resolved = this.lookupPath(context, head);
3209
3283
  if (resolved === undefined) {
3210
- const evalRes = this.evaluator.evaluate(head, context);
3284
+ const candidateExpr = this.hasTemplateTokens(head)
3285
+ ? head
3286
+ .replace(/\$\{([^}]+)\}/g, (_m, inner) => {
3287
+ const val = this.resolveKpiPipeChain(String(inner).trim(), context);
3288
+ return val !== undefined && val !== null && !isUnresolvedTemplateToken(val)
3289
+ ? String(val)
3290
+ : 'undefined';
3291
+ })
3292
+ .replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_m, inner) => {
3293
+ const val = this.resolveKpiPipeChain(String(inner).trim(), context);
3294
+ return val !== undefined && val !== null && !isUnresolvedTemplateToken(val)
3295
+ ? String(val)
3296
+ : 'undefined';
3297
+ })
3298
+ : head;
3299
+ const evalRes = this.evaluator.evaluate(candidateExpr, context);
3211
3300
  if (!evalRes.error && evalRes.value !== undefined) {
3212
3301
  resolved = evalRes.value;
3213
3302
  }
@@ -6588,6 +6677,12 @@ class CrudMetadataEditorComponent {
6588
6677
  const nextPayload = { ...(current.payload || {}) };
6589
6678
  if (field) {
6590
6679
  nextPayload['field'] = field;
6680
+ if (!nextPayload['filter'] || typeof nextPayload['filter'] !== 'object' || Array.isArray(nextPayload['filter'])) {
6681
+ nextPayload['filter'] = {};
6682
+ }
6683
+ if (!nextPayload['metric'] && (!Array.isArray(nextPayload['metrics']) || nextPayload['metrics'].length === 0)) {
6684
+ nextPayload['metrics'] = [{ operation: 'COUNT', alias: 'total' }];
6685
+ }
6591
6686
  }
6592
6687
  else {
6593
6688
  delete nextPayload['field'];
@@ -6604,17 +6699,35 @@ class CrudMetadataEditorComponent {
6604
6699
  return;
6605
6700
  }
6606
6701
  const normalizedPath = rawPath.startsWith('/') ? rawPath : `/${rawPath}`;
6607
- const defaultField = this.availableResourceFieldOptions()[0]?.field || 'status';
6608
- this.updateKpiBand((current) => ({
6609
- ...current,
6610
- enabled: true,
6611
- source: `${normalizedPath}/stats/group-by`,
6612
- method: 'POST',
6613
- payload: {
6614
- ...(current.payload || {}),
6615
- field: this.kpiBandGroupByField() || defaultField,
6616
- },
6617
- }));
6702
+ const options = this.availableResourceFieldOptions();
6703
+ const preferredNames = ['ativo', 'status', 'situacao', 'active', 'enabled', 'tipo', 'categoria'];
6704
+ const preferredField = options.find((opt) => preferredNames.includes(opt.field.toLowerCase()))?.field ||
6705
+ options[0]?.field ||
6706
+ 'status';
6707
+ const defaultField = preferredField;
6708
+ this.updateKpiBand((current) => {
6709
+ const existingPayload = current.payload && typeof current.payload === 'object' && !Array.isArray(current.payload)
6710
+ ? current.payload
6711
+ : {};
6712
+ const hasMetrics = !!existingPayload['metric'] ||
6713
+ (Array.isArray(existingPayload['metrics']) && existingPayload['metrics'].length > 0);
6714
+ return {
6715
+ ...current,
6716
+ enabled: true,
6717
+ source: `${normalizedPath}/stats/group-by`,
6718
+ method: 'POST',
6719
+ payload: {
6720
+ ...existingPayload,
6721
+ field: this.kpiBandGroupByField() || defaultField,
6722
+ filter: existingPayload['filter'] &&
6723
+ typeof existingPayload['filter'] === 'object' &&
6724
+ !Array.isArray(existingPayload['filter'])
6725
+ ? existingPayload['filter']
6726
+ : {},
6727
+ ...(hasMetrics ? {} : { metrics: [{ operation: 'COUNT', alias: 'total' }] }),
6728
+ },
6729
+ };
6730
+ });
6618
6731
  }
6619
6732
  addKpiCard() {
6620
6733
  this.updateKpiBand((current) => {
@@ -8899,7 +9012,7 @@ class CrudMetadataEditorComponent {
8899
9012
  </mat-tab>
8900
9013
  </mat-tab-group>
8901
9014
  </section>
8902
- `, isInline: true, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface,#1a1b20)}.editor-shell{display:grid;gap:16px}.editor-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.editor-overview,.editor-health-map{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.editor-health-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-health-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-health-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus{display:flex;gap:12px;align-items:start;padding:14px 16px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-focus--success{background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.editor-focus-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent);white-space:nowrap}.editor-focus-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-focus-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus-copy{display:grid;gap:4px}.editor-focus-copy p{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-tabs{display:flex;flex-direction:column;min-width:0}.editor-tabs .mat-mdc-tab{min-width:118px}.editor-tabs ::ng-deep .mdc-tab__content{gap:8px}.editor-tabs ::ng-deep .mat-mdc-tab-body-wrapper,.editor-tabs ::ng-deep .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.editor-tabs ::ng-deep .mat-mdc-tab-body-content{overflow:hidden}.tab-status-dot{width:8px;height:8px;border-radius:999px;background:var(--md-sys-color-primary,#1263b4);flex:0 0 auto}.tab-status-dot--warn{background:var(--md-sys-color-tertiary,#7d5700)}.tab-status-dot--error{background:var(--md-sys-color-error,#b3261e)}.tab-content{display:grid;gap:14px;min-width:0;padding:12px 2px 4px;box-sizing:border-box}.editor-overview-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-overview-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-overview-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-overview-label{font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-value{line-height:1.3;overflow-wrap:anywhere}.editor-overview-note{margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-header h2,.editor-card h3{margin:0}.editor-header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-chip{padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary,#1263b4) 12%,transparent);font-size:12px}.editor-card{display:grid;gap:14px;padding:16px;border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 70%,transparent)}.editor-section-header{display:flex;justify-content:space-between;gap:12px;align-items:start;flex-wrap:wrap}.editor-toolbar-row{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap}.editor-inline-grow{flex:1 1 240px}.inline-actions-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.inline-chip{display:inline-flex;align-items:center;gap:6px}.chip-remove-btn{border:0;background:transparent;cursor:pointer;font-size:14px;line-height:1;padding:0 2px;color:inherit}.kpi-cards-list{display:grid;gap:12px}.inherited-columns-banner{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap;padding:12px 14px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 55%,transparent)}.inherited-columns-banner__copy{display:grid;gap:4px}.inherited-columns-banner__title-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.editor-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid--compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.editor-toggles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-actions,.action-grid{display:grid;gap:14px}.action-summary-strip{display:flex;flex-wrap:wrap;gap:8px}.action-summary-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.action-summary-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.action-summary-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.action-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 88%,transparent)}.action-group--advanced{background:color-mix(in srgb,var(--md-sys-color-surface-container,#f5f7fb) 90%,transparent)}.action-group__header{display:grid;gap:4px}.action-subgroup{display:grid;gap:10px;padding:10px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low,#fafbfd) 92%,transparent)}.action-subgroup__header{display:grid;gap:4px}.action-param-list{display:grid;gap:12px}.action-param-row{display:grid;gap:8px}.action-advanced-panel{border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 65%,transparent);border-radius:16px}.editor-section-note{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.diagnostics-groups{display:grid;gap:12px}.diagnostics-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.diagnostics{display:grid;gap:8px;margin:0;padding-left:18px}.diagnostics .error{color:var(--md-sys-color-error,#b3261e)}pre{margin:0;overflow:auto;max-height:320px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4$3.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$3.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$3.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4$3.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i9.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisKpiBandComponent, selector: "praxis-kpi-band", inputs: ["config", "dataContext", "isInteractiveHost", "activeFilter"], outputs: ["cardClick", "statsLoaded", "statsError", "refreshClick"] }, { kind: "component", type: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "fieldMetadata", "readonly"], outputs: ["configChange"] }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9015
+ `, isInline: true, styles: [":host{display:block;min-width:0;color:var(--md-sys-color-on-surface,#1a1b20)}.editor-shell{display:grid;gap:16px}.editor-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.editor-overview,.editor-health-map{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}.editor-health-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-health-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-health-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus{display:flex;gap:12px;align-items:start;padding:14px 16px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-focus--success{background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.editor-focus-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent);white-space:nowrap}.editor-focus-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-focus-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-focus-copy{display:grid;gap:4px}.editor-focus-copy p{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-tabs{display:flex;flex-direction:column;min-width:0}.editor-tabs .mat-mdc-tab{min-width:118px}.editor-tabs ::ng-deep .mdc-tab__content{gap:8px}.editor-tabs ::ng-deep .mat-mdc-tab-body-wrapper,.editor-tabs ::ng-deep .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.editor-tabs ::ng-deep .mat-mdc-tab-body-content{overflow:hidden}.tab-status-dot{width:8px;height:8px;border-radius:999px;background:var(--md-sys-color-primary,#1263b4);flex:0 0 auto}.tab-status-dot--warn{background:var(--md-sys-color-tertiary,#7d5700)}.tab-status-dot--error{background:var(--md-sys-color-error,#b3261e)}.tab-content{display:grid;gap:14px;min-width:0;padding:12px 2px 4px;box-sizing:border-box}.editor-overview-card{display:grid;gap:4px;padding:14px;border-radius:18px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.editor-overview-card--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.editor-overview-card--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.editor-overview-label{font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-overview-value{line-height:1.3;overflow-wrap:anywhere}.editor-overview-note{margin:0;font-size:12px;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-header h2,.editor-card h3{margin:0}.editor-header p{margin:6px 0 0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.editor-chip{padding:6px 10px;border-radius:999px;background:color-mix(in srgb,var(--md-sys-color-primary,#1263b4) 12%,transparent);font-size:12px}.editor-card{display:grid;gap:14px;padding:16px;border-radius:20px;border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 70%,transparent)}.editor-section-header{display:flex;justify-content:space-between;gap:12px;align-items:start;flex-wrap:wrap}.editor-toolbar-row{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap}.editor-inline-grow{flex:1 1 240px}.inline-actions-row{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.inline-chip{display:inline-flex;align-items:center;gap:6px}.chip-remove-btn{border:0;background:transparent;cursor:pointer;font-size:14px;line-height:1;padding:0 2px;color:inherit}.kpi-cards-list{display:grid;gap:12px}.inherited-columns-banner{display:flex;justify-content:space-between;gap:12px;align-items:center;flex-wrap:wrap;padding:12px 14px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 55%,transparent)}.inherited-columns-banner__copy{display:grid;gap:4px}.inherited-columns-banner__title-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.editor-grid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid--compact{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.editor-toggles{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-actions,.action-grid{display:grid;gap:14px}.action-summary-strip{display:flex;flex-wrap:wrap;gap:8px}.action-summary-chip{padding:6px 10px;border-radius:999px;font-size:12px;background:color-mix(in srgb,var(--md-sys-color-secondary-container,#d7e3ff) 70%,transparent)}.action-summary-chip--warn{background:color-mix(in srgb,var(--md-sys-color-tertiary-container,#f8e08e) 78%,transparent)}.action-summary-chip--error{background:color-mix(in srgb,var(--md-sys-color-error-container,#f9dedc) 82%,transparent);color:var(--md-sys-color-on-error-container,#410e0b)}.action-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 88%,transparent)}.action-group--advanced{background:color-mix(in srgb,var(--md-sys-color-surface-container,#f5f7fb) 90%,transparent)}.action-group__header{display:grid;gap:4px}.action-subgroup{display:grid;gap:10px;padding:10px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-low,#fafbfd) 92%,transparent)}.action-subgroup__header{display:grid;gap:4px}.action-param-list{display:grid;gap:12px}.action-param-row{display:grid;gap:8px}.action-advanced-panel{border:1px solid color-mix(in srgb,var(--md-sys-color-outline,#c5c7ce) 65%,transparent);border-radius:16px}.editor-section-note{margin:0;color:var(--md-sys-color-on-surface-variant,#5a5d67)}.diagnostics-groups{display:grid;gap:12px}.diagnostics-group{display:grid;gap:10px;padding:12px;border-radius:16px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}.diagnostics{display:grid;gap:8px;margin:0;padding-left:18px}.diagnostics .error{color:var(--md-sys-color-error,#b3261e)}pre{margin:0;overflow:auto;max-height:320px;padding:12px;border-radius:14px;background:color-mix(in srgb,var(--md-sys-color-surface-container-high,#eef1f6) 92%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i3$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4$3.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$3.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$3.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4$3.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i6.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i6.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i8.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i9.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i9.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i9.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisKpiBandComponent, selector: "praxis-kpi-band", inputs: ["config", "dataContext", "isInteractiveHost", "activeFilter"], outputs: ["cardClick", "statsLoaded", "statsError", "refreshClick"] }, { kind: "component", type: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "fieldMetadata", "readonly"], outputs: ["configChange"] }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange", "resourceSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8903
9016
  }
8904
9017
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudMetadataEditorComponent, decorators: [{
8905
9018
  type: Component,
@@ -10461,6 +10574,7 @@ class PraxisCrudComponent {
10461
10574
  destroyRef = inject(DestroyRef);
10462
10575
  cdr = inject(ChangeDetectorRef);
10463
10576
  widgetEventBus = inject(PraxisWidgetEventBus, { optional: true });
10577
+ kpiBand;
10464
10578
  _table;
10465
10579
  set table(value) {
10466
10580
  this._table = value;
@@ -11164,6 +11278,10 @@ class PraxisCrudComponent {
11164
11278
  if (handledByDuplicateDraft) {
11165
11279
  return;
11166
11280
  }
11281
+ const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, contextualRow);
11282
+ if (handledByDelete) {
11283
+ return;
11284
+ }
11167
11285
  if (!this.hasExplicitOpenBinding(effectiveAction)) {
11168
11286
  const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(effectiveAction, contextualRow, runtimeEvent);
11169
11287
  if (openedByDiscovery) {
@@ -11174,10 +11292,6 @@ class PraxisCrudComponent {
11174
11292
  return;
11175
11293
  }
11176
11294
  }
11177
- const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, contextualRow);
11178
- if (handledByDelete) {
11179
- return;
11180
- }
11181
11295
  let drawerCloseEmitted = false;
11182
11296
  const emitDrawerClose = () => {
11183
11297
  if (drawerCloseEmitted)
@@ -11481,6 +11595,7 @@ class PraxisCrudComponent {
11481
11595
  type: 'warning',
11482
11596
  },
11483
11597
  });
11598
+ this.openingActionLabel.set(null);
11484
11599
  this.afterOpen.emit({ mode: 'modal', action: action.action });
11485
11600
  const confirmed = await firstValueFrom(ref.afterClosed());
11486
11601
  this.afterClose.emit();
@@ -11567,6 +11682,7 @@ class PraxisCrudComponent {
11567
11682
  return true;
11568
11683
  }
11569
11684
  refreshTable() {
11685
+ this.kpiBand?.refresh?.();
11570
11686
  if (this.tryRefreshMaterializedLocalData()) {
11571
11687
  return;
11572
11688
  }
@@ -12530,7 +12646,10 @@ class PraxisCrudComponent {
12530
12646
  }
12531
12647
  }
12532
12648
  }
12533
- const hostDefinedRow = !!(base && base.actions && base.actions.row);
12649
+ const hostDefinedRow = !!(base &&
12650
+ base.actions &&
12651
+ Array.isArray(base.actions.row?.actions) &&
12652
+ base.actions.row.actions.length > 0);
12534
12653
  const crudDefaults = this.global?.get('crud.defaults') || {};
12535
12654
  const autoRow = crudDefaults.autoRowActions !== false;
12536
12655
  if (!hostDefinedRow && autoRow) {
@@ -13228,7 +13347,7 @@ class PraxisCrudComponent {
13228
13347
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", resource: "resource", componentInstanceId: "componentInstanceId", tableConfigPersistenceStrategy: "tableConfigPersistenceStrategy", context: "context", enableCustomization: "enableCustomization", showResourceIdentityKey: "showResourceIdentityKey", authoringCapability: "authoringCapability", filterCriteria: "filterCriteria", filterTeleport: ["filterTeleport", "filterTeleport", booleanAttribute], disableAutoDrawer: ["disableAutoDrawer", "disableAutoDrawer", booleanAttribute] }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", reactiveDeterminationExecuted: "reactiveDeterminationExecuted", reactiveDeterminationPendingChange: "reactiveDeterminationPendingChange", reactiveDeterminationStabilityChange: "reactiveDeterminationStabilityChange", error: "error", rowClick: "rowClick", rowAction: "rowAction", selectionChange: "selectionChange", resourceEvent: "resourceEvent", tableRuntimeConfigChange: "tableRuntimeConfigChange", drawerOpen: "drawerOpen", drawerClose: "drawerClose", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved", filterCriteriaChange: "filterCriteriaChange", kpiCardClick: "kpiCardClick" }, providers: [
13229
13348
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
13230
13349
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
13231
- ], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
13350
+ ], viewQueries: [{ propertyName: "kpiBand", first: true, predicate: PraxisKpiBandComponent, descendants: true }, { propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
13232
13351
  <div class="praxis-crud-runtime" [attr.aria-busy]="openingActionLabel() ? 'true' : null">
13233
13352
  @if (openingActionLabel(); as actionLabel) {
13234
13353
  <div class="praxis-crud-opening-status" role="status" aria-live="polite" data-testid="praxis-crud-opening-status">
@@ -13412,6 +13531,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
13412
13531
  type: Output
13413
13532
  }], kpiCardClick: [{
13414
13533
  type: Output
13534
+ }], kpiBand: [{
13535
+ type: ViewChild,
13536
+ args: [PraxisKpiBandComponent]
13415
13537
  }], table: [{
13416
13538
  type: ViewChild,
13417
13539
  args: [PraxisTable]
@@ -15224,14 +15346,16 @@ const ENUMS = {
15224
15346
  toolbarActionAppearance: ['filled', 'outlined', 'elevated', 'text', 'tonal'],
15225
15347
  toolbarActionPosition: ['start', 'end'],
15226
15348
  backStrategy: ['auto', 'close', 'navigate'],
15349
+ kpiBandMethod: ['GET', 'POST'],
15227
15350
  };
15228
15351
  const CRUD_AI_CAPABILITIES = {
15229
- version: 'v1.0',
15352
+ version: 'v1.1',
15230
15353
  enums: ENUMS,
15231
15354
  targets: ['praxis-crud', 'crud-view', 'generic-crud'],
15232
15355
  notes: [
15233
15356
  'CrudMetadata agrega TableConfig e FormConfig; use os catalogos de tabela e formulario para detalhes.',
15234
15357
  'actions[] herda campos de RowAction e ToolbarAction; combine com openMode/route/formId quando aplicavel.',
15358
+ 'kpiBand renderiza cards executivos orientados a endpoint ou projecao declarativa; valueExpr e captionExpr suportam dot-paths e pipes declarativos CSP-safe (default:<fallback>, number, round[:decimals], percent[:decimals], currency[:code], uppercase, lowercase).',
15235
15359
  ],
15236
15360
  capabilities: [
15237
15361
  // --- Metadata root ---
@@ -15254,9 +15378,20 @@ const CRUD_AI_CAPABILITIES = {
15254
15378
  { path: 'queryContext.meta.domainCatalog.releaseId', category: 'resource', valueKind: 'string', description: 'Release opcional do Domain Catalog usada como fonte do contexto.' },
15255
15379
  { path: 'queryContext.meta.domainCatalog.query', category: 'resource', valueKind: 'string', description: 'Probe de campo/conceito usado para buscar itens relevantes, como cpf, salario ou status.' },
15256
15380
  { path: 'queryContext.meta.domainCatalog.intent', category: 'resource', valueKind: 'enum', allowedValues: ['authoring', 'explain', 'validate', 'ai-access-control'], description: 'Intencao de uso do contexto para orientar LLMs e validadores sem expor dados alem do necessario.' },
15257
- // --- Table / Form ---
15381
+ // --- Table / Form / KPI Band ---
15258
15382
  { path: 'table', category: 'table', valueKind: 'object', description: 'TableConfig completo (usar catalogo de tabela).' },
15259
15383
  { path: 'form', category: 'form', valueKind: 'object', description: 'FormConfig completo (usar catalogo de formulario).' },
15384
+ { path: 'kpiBand', category: 'table', valueKind: 'object', description: 'Faixa executiva declarativa de KPIs acima da listagem principal.' },
15385
+ { path: 'kpiBand.source', category: 'table', valueKind: 'string', description: 'Path relativo do endpoint de KPIs/stats (ex: /stats, /stats/group-by).' },
15386
+ { path: 'kpiBand.method', category: 'table', valueKind: 'enum', allowedValues: ENUMS.kpiBandMethod, description: 'Metodo HTTP da consulta de KPIs (GET ou POST).' },
15387
+ { path: 'kpiBand.payload', category: 'table', valueKind: 'object', description: 'Payload JSON enviado quando kpiBand.method = POST.' },
15388
+ { path: 'kpiBand.cards', category: 'table', valueKind: 'array', description: 'Lista declarativa de cards executivos da faixa de KPIs.' },
15389
+ { path: 'kpiBand.cards[].id', category: 'table', valueKind: 'string', description: 'Identificador estavel do card de KPI.' },
15390
+ { path: 'kpiBand.cards[].label', category: 'table', valueKind: 'string', description: 'Rotulo executivo do card de KPI.' },
15391
+ { path: 'kpiBand.cards[].icon', category: 'table', valueKind: 'string', description: 'Icone Material opcional do card de KPI.' },
15392
+ { path: 'kpiBand.cards[].valueExpr', category: 'table', valueKind: 'string', description: 'Dot-path no payload de stats com suporte a pipes declarativos CSP-safe (ex: totalCount | default:0 | number).' },
15393
+ { path: 'kpiBand.cards[].captionExpr', category: 'table', valueKind: 'string', description: 'Subtitulo ou expressao declarativa complementar com suporte a pipes CSP-safe.' },
15394
+ { path: 'kpiBand.cards[].clickFilter', category: 'table', valueKind: 'object', description: 'Mapa declarativo de filtro rapido aplicado na tabela ao clicar no card de KPI.' },
15260
15395
  // --- Defaults ---
15261
15396
  { path: 'defaults', category: 'defaults', valueKind: 'object', description: 'Defaults aplicados a acoes e UI.' },
15262
15397
  { path: 'defaults.openMode', category: 'defaults', valueKind: 'enum', allowedValues: ENUMS.openMode, description: 'Modo padrao de abertura (route/modal/drawer).' },
@@ -15969,6 +16104,13 @@ class PraxisResourcePage {
15969
16104
  const meta = this.resolveParsedMetadataSource();
15970
16105
  return Array.isArray(meta?.data);
15971
16106
  }, ...(ngDevMode ? [{ debugName: "hasLocalData" }] : /* istanbul ignore next */ []));
16107
+ hasCrudSeed = computed(() => {
16108
+ this.inputsVersion();
16109
+ return !!(this.crudId ||
16110
+ this.routeCrudIdSignal() ||
16111
+ this.metadata ||
16112
+ this.routeMetadataSignal());
16113
+ }, ...(ngDevMode ? [{ debugName: "hasCrudSeed" }] : /* istanbul ignore next */ []));
15972
16114
  effectiveResource = computed(() => {
15973
16115
  this.inputsVersion();
15974
16116
  return (this.resource ||
@@ -16586,9 +16728,12 @@ class PraxisResourcePage {
16586
16728
  }
16587
16729
 
16588
16730
  <!-- Praxis CRUD Runtime com isolamento reativo multi-tenant -->
16589
- @if ((effectiveResource() || effectiveEnableCustomization() || hasLocalData()) && identityVisible()) {
16731
+ @if ((effectiveResource() || effectiveEnableCustomization() || hasLocalData() || hasCrudSeed()) && identityVisible()) {
16590
16732
  @for (renderKey of [identityRenderKey()]; track renderKey) {
16591
- <section class="praxis-resource-page__crud-surface crud-surface glass-panel">
16733
+ <section
16734
+ class="praxis-resource-page__crud-surface crud-surface glass-panel"
16735
+ [style.display]="(effectiveResource() || effectiveEnableCustomization() || hasLocalData()) ? null : 'none'"
16736
+ >
16592
16737
  <praxis-crud
16593
16738
  [resource]="effectiveResource() || ''"
16594
16739
  [crudId]="resolvedCrudId()"
@@ -16698,9 +16843,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
16698
16843
  }
16699
16844
 
16700
16845
  <!-- Praxis CRUD Runtime com isolamento reativo multi-tenant -->
16701
- @if ((effectiveResource() || effectiveEnableCustomization() || hasLocalData()) && identityVisible()) {
16846
+ @if ((effectiveResource() || effectiveEnableCustomization() || hasLocalData() || hasCrudSeed()) && identityVisible()) {
16702
16847
  @for (renderKey of [identityRenderKey()]; track renderKey) {
16703
- <section class="praxis-resource-page__crud-surface crud-surface glass-panel">
16848
+ <section
16849
+ class="praxis-resource-page__crud-surface crud-surface glass-panel"
16850
+ [style.display]="(effectiveResource() || effectiveEnableCustomization() || hasLocalData()) ? null : 'none'"
16851
+ >
16704
16852
  <praxis-crud
16705
16853
  [resource]="effectiveResource() || ''"
16706
16854
  [crudId]="resolvedCrudId()"
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@praxisui/crud",
3
- "version": "10.0.0-rc.33",
3
+ "version": "10.0.0-rc.34",
4
4
  "description": "CRUD building blocks for Praxis UI: integrates dynamic forms and tables with unified configuration and services.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^21.0.0",
7
7
  "@angular/core": "^21.0.0",
8
- "@praxisui/dynamic-form": "^10.0.0-rc.33",
9
- "@praxisui/table": "^10.0.0-rc.33",
10
- "@praxisui/core": "^10.0.0-rc.33",
11
- "@praxisui/dynamic-fields": "^10.0.0-rc.33",
12
- "@praxisui/settings-panel": "^10.0.0-rc.33",
8
+ "@praxisui/dynamic-form": "^10.0.0-rc.34",
9
+ "@praxisui/table": "^10.0.0-rc.34",
10
+ "@praxisui/core": "^10.0.0-rc.34",
11
+ "@praxisui/dynamic-fields": "^10.0.0-rc.34",
12
+ "@praxisui/settings-panel": "^10.0.0-rc.34",
13
13
  "@angular/cdk": "^21.0.0",
14
14
  "@angular/forms": "^21.0.0",
15
15
  "@angular/material": "^21.0.0",
16
16
  "@angular/router": "^21.0.0",
17
- "@praxisui/ai": "^10.0.0-rc.33",
17
+ "@praxisui/ai": "^10.0.0-rc.34",
18
18
  "rxjs": "~7.8.0"
19
19
  },
20
20
  "dependencies": {
@@ -324,6 +324,7 @@ declare class PraxisCrudComponent implements OnChanges {
324
324
  private readonly destroyRef;
325
325
  private readonly cdr;
326
326
  private readonly widgetEventBus;
327
+ private kpiBand?;
327
328
  private _table?;
328
329
  private set table(value);
329
330
  private get table();
@@ -1167,6 +1168,7 @@ declare class PraxisKpiBandComponent implements OnInit, OnChanges {
1167
1168
  refresh(): void;
1168
1169
  private resolveSourceUrl;
1169
1170
  private fetchFromSource;
1171
+ private normalizeSourceRequestPayload;
1170
1172
  private normalizeSourcePayload;
1171
1173
  recomputeCards(): void;
1172
1174
  private hasTemplateTokens;
@@ -1294,6 +1296,7 @@ declare class PraxisResourcePage implements OnInit, OnChanges, OnDestroy {
1294
1296
  readonly activeScopeId: _angular_core.WritableSignal<string>;
1295
1297
  private readonly activeScopeFilter;
1296
1298
  readonly hasLocalData: _angular_core.Signal<boolean>;
1299
+ readonly hasCrudSeed: _angular_core.Signal<boolean>;
1297
1300
  readonly effectiveResource: _angular_core.Signal<string | null>;
1298
1301
  readonly effectiveTitle: _angular_core.Signal<string | null>;
1299
1302
  readonly effectiveSubtitle: _angular_core.Signal<string | null>;