@praxisui/table 1.0.0-beta.19 → 1.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -430,6 +430,10 @@ Quando `behavior.virtualization.enabled` estiver ativo, as linhas da tabela são
430
430
  - `behavior.pagination.position`: `top` | `bottom` | `both`.
431
431
  - `behavior.pagination.style`: `default` | `compact` (aplica classe de estilo no paginator).
432
432
 
433
+ Nota sobre estratégia (client vs server)
434
+ - Se `behavior.pagination.strategy` não estiver definido, a tabela assume `server` automaticamente quando há `resourcePath` (dados remotos). Caso contrário, usa `client`.
435
+ - O mesmo vale para `behavior.sorting.strategy`.
436
+
433
437
  ## Duplo clique na linha
434
438
 
435
439
  Ative em Comportamento → Interação.
@@ -491,6 +495,8 @@ Observação: quando informado, o defaultSort é aplicado na carga inicial se n
491
495
 
492
496
  ## Coluna de ações (sticky)
493
497
 
498
+ Nota: por padrão a coluna de ações vem desabilitada. Habilite explicitamente em `actions.row.enabled` e defina as ações desejadas.
499
+
494
500
  - Fixe a coluna de ações no início/fim configurando `actions.row.sticky`:
495
501
 
496
502
  ```ts
@@ -759,6 +765,8 @@ export class ExampleComponent {
759
765
 
760
766
  Quando a `<praxis-table>` é conectada a um `resourcePath`, as operações de paginação, ordenação e filtro são delegadas ao backend. Isso garante alta performance, pois apenas os dados visíveis na tela são trafegados pela rede.
761
767
 
768
+ Importante: se você não configurar explicitamente as estratégias de paginação/ordenação no `TableConfig`, a tabela resolve automaticamente como `server` quando há `resourcePath`. Se preferir operar no cliente, defina `behavior.pagination.strategy = 'client'` e/ou `behavior.sorting.strategy = 'client'` conscientemente.
769
+
762
770
  O diagrama abaixo detalha a sequência de eventos, desde a interação do usuário na UI até a construção da consulta JPA no servidor.
763
771
 
764
772
  ```mermaid
@@ -9077,6 +9077,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
9077
9077
  class BehaviorConfigEditorComponent {
9078
9078
  fb;
9079
9079
  config = { columns: [] };
9080
+ // Opcional: informa se há recurso remoto para ajustar defaults
9081
+ resourcePath;
9080
9082
  configChange = new EventEmitter();
9081
9083
  behaviorChange = new EventEmitter();
9082
9084
  behaviorForm;
@@ -9108,7 +9110,8 @@ class BehaviorConfigEditorComponent {
9108
9110
  this.arrayToString(pagination.pageSizeOptions || [5, 10, 25, 50]),
9109
9111
  ],
9110
9112
  showFirstLastButtons: [pagination.showFirstLastButtons !== false],
9111
- paginationStrategy: ['client'], // V2 only
9113
+ // Default adaptado: se houver resourcePath e não houver override, usar 'server'
9114
+ paginationStrategy: [this.resourcePath ? 'server' : 'client'], // V2 only
9112
9115
  paginationPosition: ['bottom'],
9113
9116
  paginationStyle: ['default'],
9114
9117
  // Sorting
@@ -9156,7 +9159,8 @@ class BehaviorConfigEditorComponent {
9156
9159
  pageSize: behavior.pagination.pageSize || 10,
9157
9160
  pageSizeOptions: this.arrayToString(behavior.pagination.pageSizeOptions || [5, 10, 25, 50]),
9158
9161
  showFirstLastButtons: behavior.pagination.showFirstLastButtons !== false,
9159
- paginationStrategy: behavior.pagination.strategy || 'client',
9162
+ // Se a estratégia não estiver definida, respeitar contexto de resourcePath
9163
+ paginationStrategy: behavior.pagination.strategy ?? (this.resourcePath ? 'server' : 'client'),
9160
9164
  paginationPosition: behavior.pagination.position || 'bottom',
9161
9165
  paginationStyle: behavior.pagination.style || 'default',
9162
9166
  });
@@ -9375,7 +9379,7 @@ class BehaviorConfigEditorComponent {
9375
9379
  .filter((n) => !isNaN(n));
9376
9380
  }
9377
9381
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BehaviorConfigEditorComponent, deps: [{ token: i1$1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
9378
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: BehaviorConfigEditorComponent, isStandalone: true, selector: "behavior-config-editor", inputs: { config: "config" }, outputs: { configChange: "configChange", behaviorChange: "behaviorChange" }, ngImport: i0, template: `
9382
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: BehaviorConfigEditorComponent, isStandalone: true, selector: "behavior-config-editor", inputs: { config: "config", resourcePath: "resourcePath" }, outputs: { configChange: "configChange", behaviorChange: "behaviorChange" }, ngImport: i0, template: `
9379
9383
  <div class="behavior-config-container">
9380
9384
  <form [formGroup]="behaviorForm">
9381
9385
  <!-- Paginação -->
@@ -10259,6 +10263,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
10259
10263
  `, styles: [".behavior-config-container{width:100%;padding:8px}.config-section{padding:16px}.config-fields{display:flex;flex-direction:column;gap:16px;margin-top:16px}.toggle-field{display:flex;align-items:center;gap:8px}.section-icon{margin-right:8px;color:var(--mat-sys-primary)}.info-icon{font-size:18px;width:18px;height:18px;color:var(--mat-sys-on-surface-variant);cursor:help}mat-form-field{width:100%}mat-expansion-panel{margin-bottom:8px;border-radius:8px;overflow:hidden}mat-expansion-panel-header{min-height:56px}mat-panel-description{color:var(--mat-sys-on-surface-variant)}\n"] }]
10260
10264
  }], ctorParameters: () => [{ type: i1$1.FormBuilder }], propDecorators: { config: [{
10261
10265
  type: Input
10266
+ }], resourcePath: [{
10267
+ type: Input
10262
10268
  }], configChange: [{
10263
10269
  type: Output
10264
10270
  }], behaviorChange: [{
@@ -19452,7 +19458,7 @@ class PraxisTableConfigEditor {
19452
19458
  this.isBusy$.complete();
19453
19459
  }
19454
19460
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisTableConfigEditor, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$3.TableConfigService }, { token: SETTINGS_PANEL_DATA }, { token: SETTINGS_PANEL_REF, optional: true }], target: i0.ɵɵFactoryTarget.Component });
19455
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisTableConfigEditor, isStandalone: true, selector: "praxis-table-config-editor", providers: [TableConfigService], viewQueries: [{ propertyName: "behaviorEditor", first: true, predicate: BehaviorConfigEditorComponent, descendants: true }, { propertyName: "crudEditorSetter", first: true, predicate: ["crudEditorRef"], descendants: true }], ngImport: i0, template: " <mat-tab-group class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n <mat-tab *ngFor=\"let section of sections\">\n <ng-template mat-tab-label>\n <mat-icon *ngIf=\"section.icon\" [praxisIcon]=\"section.icon\"></mat-icon>\n <span>{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n <ng-container [ngSwitch]=\"section.id\">\n <div *ngSwitchCase=\"'connect'\" style=\"display:grid; gap:12px; padding: 8px; grid-template-columns: 1fr 240px; align-items: start;\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Recurso</mat-label>\n <input matInput [(ngModel)]=\"resourcePath\" (ngModelChange)=\"onResourcePathChange($event)\" placeholder=\"ex.: employees\" />\n <mat-icon matSuffix>link</mat-icon>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Chave prim\u00E1ria</mat-label>\n <input matInput [(ngModel)]=\"idField\" (ngModelChange)=\"onIdFieldChange($event)\" placeholder=\"ex.: id, uuid, codigo\" />\n <mat-icon matSuffix>fingerprint</mat-icon>\n </mat-form-field>\n <small style=\"opacity:.75\">Defina o recurso da API e, se necess\u00E1rio, a chave prim\u00E1ria</small>\n\n <!-- Diverg\u00EAncia: idField -->\n <div *ngIf=\"idFieldDiverges\" style=\"grid-column: 1 / -1; padding: 8px 12px; border-radius: 6px; background: #fff4e5; color: #8a4b00; display:flex; align-items:center; gap: 8px;\">\n <mat-icon>warning</mat-icon>\n <div style=\"flex:1;\">\n A chave prim\u00E1ria no servidor diverge da configura\u00E7\u00E3o atual.\n <span style=\"opacity:.85\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">Reconciliar</button>\n </div>\n\n <!-- Diverg\u00EAncia: serverHash -->\n <div *ngIf=\"schemaHashDiverges\" style=\"grid-column: 1 / -1; padding: 8px 12px; border-radius: 6px; background: #e6f4ff; color: #0b5aaa; display:flex; align-items:center; gap: 8px;\">\n <mat-icon>info</mat-icon>\n <div style=\"flex:1;\">\n O schema no servidor foi atualizado desde a \u00FAltima sincroniza\u00E7\u00E3o.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Atualizar metadados</button>\n </div>\n </div>\n\n <div *ngSwitchCase=\"'overview'\" class=\"overview-grid\">\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>Scroll Horizontal</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">Auto (padr\u00E3o)</mat-option>\n <mat-option value=\"wrap\">Wrap (quebrar linhas)</mat-option>\n <mat-option value=\"none\">Host controla</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">Controla como a tabela lida com largura horizontal e barra de rolagem.</div>\n </div>\n <behavior-config-editor\n [config]=\"editedConfig\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n\n <columns-config-editor\n *ngSwitchCase=\"'columns'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n\n <table-rules-editor\n *ngSwitchCase=\"'rules'\"\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n\n <header-appearance-editor\n *ngSwitchCase=\"'header'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n\n <toolbar-actions-editor\n *ngSwitchCase=\"'toolbar'\"\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n ></toolbar-actions-editor>\n\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n <crud-integration-editor\n #crudEditorRef\n *ngSwitchCase=\"'crud'\"\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n ></crud-integration-editor>\n\n <filter-settings\n *ngSwitchCase=\"'filters'\"\n [metadata]=\"columnMetas\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n\n <messages-localization-editor\n *ngSwitchCase=\"'messages'\"\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n\n <confirm-dialog-appearance-editor\n *ngSwitchCase=\"'dialogs'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n\n\n <json-config-editor\n *ngSwitchCase=\"'json'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n </ng-container>\n </div>\n </mat-tab>\n </mat-tab-group>\n<div class=\"config-editor-status\" *ngIf=\"statusMessage\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n</div>\n", styles: ["@charset \"UTF-8\";.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.educational-card{margin-bottom:24px;background:var(--mat-sys-surface-container-low);border-left:4px solid var(--mat-sys-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid rgba(0,0,0,.12);background:var(--mat-sys-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--mat-sys-error)}.status-text.success{color:var(--mat-sys-primary)}@media (max-width: 768px){.tab-content{padding:8px}.config-editor-status{padding:12px 16px}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i4$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i4$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i4$1.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: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.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: i5$1.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: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: JsonConfigEditorComponent, selector: "json-config-editor", inputs: ["config"], outputs: ["configChange", "validationChange", "editorEvent"] }, { kind: "component", type: ColumnsConfigEditorComponent, selector: "columns-config-editor", inputs: ["config"], outputs: ["configChange", "columnChange"] }, { kind: "component", type: BehaviorConfigEditorComponent, selector: "behavior-config-editor", inputs: ["config"], outputs: ["configChange", "behaviorChange"] }, { kind: "component", type: HeaderAppearanceEditorComponent, selector: "header-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: ToolbarActionsEditorComponent, selector: "toolbar-actions-editor", inputs: ["config"], outputs: ["configChange", "toolbarActionsChange"] }, { kind: "component", type: MessagesLocalizationEditorComponent, selector: "messages-localization-editor", inputs: ["config"], outputs: ["configChange", "messagesLocalizationChange"] }, { kind: "component", type: FilterSettingsComponent, selector: "filter-settings", inputs: ["metadata", "settings", "configKey"], outputs: ["settingsChange"] }, { kind: "component", type: CrudIntegrationEditorComponent, selector: "crud-integration-editor", inputs: ["tableId", "crudContext"] }, { kind: "component", type: ConfirmDialogAppearanceEditorComponent, selector: "confirm-dialog-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: TableRulesEditorComponent, selector: "table-rules-editor", inputs: ["config", "resourcePath", "fields", "i18nRules", "debugLogs", "debugLevel"], outputs: ["configChange"] }] });
19461
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisTableConfigEditor, isStandalone: true, selector: "praxis-table-config-editor", providers: [TableConfigService], viewQueries: [{ propertyName: "behaviorEditor", first: true, predicate: BehaviorConfigEditorComponent, descendants: true }, { propertyName: "crudEditorSetter", first: true, predicate: ["crudEditorRef"], descendants: true }], ngImport: i0, template: " <mat-tab-group class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n <mat-tab *ngFor=\"let section of sections\">\n <ng-template mat-tab-label>\n <mat-icon *ngIf=\"section.icon\" [praxisIcon]=\"section.icon\"></mat-icon>\n <span>{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n <ng-container [ngSwitch]=\"section.id\">\n <div *ngSwitchCase=\"'connect'\" style=\"display:grid; gap:12px; padding: 8px; grid-template-columns: 1fr 240px; align-items: start;\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Recurso</mat-label>\n <input matInput [(ngModel)]=\"resourcePath\" (ngModelChange)=\"onResourcePathChange($event)\" placeholder=\"ex.: employees\" />\n <mat-icon matSuffix>link</mat-icon>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Chave prim\u00E1ria</mat-label>\n <input matInput [(ngModel)]=\"idField\" (ngModelChange)=\"onIdFieldChange($event)\" placeholder=\"ex.: id, uuid, codigo\" />\n <mat-icon matSuffix>fingerprint</mat-icon>\n </mat-form-field>\n <small style=\"opacity:.75\">Defina o recurso da API e, se necess\u00E1rio, a chave prim\u00E1ria</small>\n\n <!-- Diverg\u00EAncia: idField -->\n <div *ngIf=\"idFieldDiverges\" style=\"grid-column: 1 / -1; padding: 8px 12px; border-radius: 6px; background: #fff4e5; color: #8a4b00; display:flex; align-items:center; gap: 8px;\">\n <mat-icon>warning</mat-icon>\n <div style=\"flex:1;\">\n A chave prim\u00E1ria no servidor diverge da configura\u00E7\u00E3o atual.\n <span style=\"opacity:.85\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">Reconciliar</button>\n </div>\n\n <!-- Diverg\u00EAncia: serverHash -->\n <div *ngIf=\"schemaHashDiverges\" style=\"grid-column: 1 / -1; padding: 8px 12px; border-radius: 6px; background: #e6f4ff; color: #0b5aaa; display:flex; align-items:center; gap: 8px;\">\n <mat-icon>info</mat-icon>\n <div style=\"flex:1;\">\n O schema no servidor foi atualizado desde a \u00FAltima sincroniza\u00E7\u00E3o.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Atualizar metadados</button>\n </div>\n </div>\n\n <div *ngSwitchCase=\"'overview'\" class=\"overview-grid\">\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>Scroll Horizontal</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">Auto (padr\u00E3o)</mat-option>\n <mat-option value=\"wrap\">Wrap (quebrar linhas)</mat-option>\n <mat-option value=\"none\">Host controla</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">Controla como a tabela lida com largura horizontal e barra de rolagem.</div>\n </div>\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n\n <columns-config-editor\n *ngSwitchCase=\"'columns'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n\n <table-rules-editor\n *ngSwitchCase=\"'rules'\"\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n\n <header-appearance-editor\n *ngSwitchCase=\"'header'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n\n <toolbar-actions-editor\n *ngSwitchCase=\"'toolbar'\"\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n ></toolbar-actions-editor>\n\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n <crud-integration-editor\n #crudEditorRef\n *ngSwitchCase=\"'crud'\"\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n ></crud-integration-editor>\n\n <filter-settings\n *ngSwitchCase=\"'filters'\"\n [metadata]=\"columnMetas\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n\n <messages-localization-editor\n *ngSwitchCase=\"'messages'\"\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n\n <confirm-dialog-appearance-editor\n *ngSwitchCase=\"'dialogs'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n\n\n <json-config-editor\n *ngSwitchCase=\"'json'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n </ng-container>\n </div>\n </mat-tab>\n </mat-tab-group>\n<div class=\"config-editor-status\" *ngIf=\"statusMessage\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n</div>\n", styles: ["@charset \"UTF-8\";.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.educational-card{margin-bottom:24px;background:var(--mat-sys-surface-container-low);border-left:4px solid var(--mat-sys-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid rgba(0,0,0,.12);background:var(--mat-sys-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--mat-sys-error)}.status-text.success{color:var(--mat-sys-primary)}@media (max-width: 768px){.tab-content{padding:8px}.config-editor-status{padding:12px 16px}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i4$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i4$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i4$1.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: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.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: i5$1.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: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: JsonConfigEditorComponent, selector: "json-config-editor", inputs: ["config"], outputs: ["configChange", "validationChange", "editorEvent"] }, { kind: "component", type: ColumnsConfigEditorComponent, selector: "columns-config-editor", inputs: ["config"], outputs: ["configChange", "columnChange"] }, { kind: "component", type: BehaviorConfigEditorComponent, selector: "behavior-config-editor", inputs: ["config", "resourcePath"], outputs: ["configChange", "behaviorChange"] }, { kind: "component", type: HeaderAppearanceEditorComponent, selector: "header-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: ToolbarActionsEditorComponent, selector: "toolbar-actions-editor", inputs: ["config"], outputs: ["configChange", "toolbarActionsChange"] }, { kind: "component", type: MessagesLocalizationEditorComponent, selector: "messages-localization-editor", inputs: ["config"], outputs: ["configChange", "messagesLocalizationChange"] }, { kind: "component", type: FilterSettingsComponent, selector: "filter-settings", inputs: ["metadata", "settings", "configKey"], outputs: ["settingsChange"] }, { kind: "component", type: CrudIntegrationEditorComponent, selector: "crud-integration-editor", inputs: ["tableId", "crudContext"] }, { kind: "component", type: ConfirmDialogAppearanceEditorComponent, selector: "confirm-dialog-appearance-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: TableRulesEditorComponent, selector: "table-rules-editor", inputs: ["config", "resourcePath", "fields", "i18nRules", "debugLogs", "debugLevel"], outputs: ["configChange"] }] });
19456
19462
  }
19457
19463
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisTableConfigEditor, decorators: [{
19458
19464
  type: Component,
@@ -19475,7 +19481,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
19475
19481
  CrudIntegrationEditorComponent,
19476
19482
  ConfirmDialogAppearanceEditorComponent,
19477
19483
  TableRulesEditorComponent,
19478
- ], providers: [TableConfigService], template: " <mat-tab-group class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n <mat-tab *ngFor=\"let section of sections\">\n <ng-template mat-tab-label>\n <mat-icon *ngIf=\"section.icon\" [praxisIcon]=\"section.icon\"></mat-icon>\n <span>{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n <ng-container [ngSwitch]=\"section.id\">\n <div *ngSwitchCase=\"'connect'\" style=\"display:grid; gap:12px; padding: 8px; grid-template-columns: 1fr 240px; align-items: start;\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Recurso</mat-label>\n <input matInput [(ngModel)]=\"resourcePath\" (ngModelChange)=\"onResourcePathChange($event)\" placeholder=\"ex.: employees\" />\n <mat-icon matSuffix>link</mat-icon>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Chave prim\u00E1ria</mat-label>\n <input matInput [(ngModel)]=\"idField\" (ngModelChange)=\"onIdFieldChange($event)\" placeholder=\"ex.: id, uuid, codigo\" />\n <mat-icon matSuffix>fingerprint</mat-icon>\n </mat-form-field>\n <small style=\"opacity:.75\">Defina o recurso da API e, se necess\u00E1rio, a chave prim\u00E1ria</small>\n\n <!-- Diverg\u00EAncia: idField -->\n <div *ngIf=\"idFieldDiverges\" style=\"grid-column: 1 / -1; padding: 8px 12px; border-radius: 6px; background: #fff4e5; color: #8a4b00; display:flex; align-items:center; gap: 8px;\">\n <mat-icon>warning</mat-icon>\n <div style=\"flex:1;\">\n A chave prim\u00E1ria no servidor diverge da configura\u00E7\u00E3o atual.\n <span style=\"opacity:.85\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">Reconciliar</button>\n </div>\n\n <!-- Diverg\u00EAncia: serverHash -->\n <div *ngIf=\"schemaHashDiverges\" style=\"grid-column: 1 / -1; padding: 8px 12px; border-radius: 6px; background: #e6f4ff; color: #0b5aaa; display:flex; align-items:center; gap: 8px;\">\n <mat-icon>info</mat-icon>\n <div style=\"flex:1;\">\n O schema no servidor foi atualizado desde a \u00FAltima sincroniza\u00E7\u00E3o.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Atualizar metadados</button>\n </div>\n </div>\n\n <div *ngSwitchCase=\"'overview'\" class=\"overview-grid\">\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>Scroll Horizontal</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">Auto (padr\u00E3o)</mat-option>\n <mat-option value=\"wrap\">Wrap (quebrar linhas)</mat-option>\n <mat-option value=\"none\">Host controla</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">Controla como a tabela lida com largura horizontal e barra de rolagem.</div>\n </div>\n <behavior-config-editor\n [config]=\"editedConfig\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n\n <columns-config-editor\n *ngSwitchCase=\"'columns'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n\n <table-rules-editor\n *ngSwitchCase=\"'rules'\"\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n\n <header-appearance-editor\n *ngSwitchCase=\"'header'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n\n <toolbar-actions-editor\n *ngSwitchCase=\"'toolbar'\"\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n ></toolbar-actions-editor>\n\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n <crud-integration-editor\n #crudEditorRef\n *ngSwitchCase=\"'crud'\"\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n ></crud-integration-editor>\n\n <filter-settings\n *ngSwitchCase=\"'filters'\"\n [metadata]=\"columnMetas\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n\n <messages-localization-editor\n *ngSwitchCase=\"'messages'\"\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n\n <confirm-dialog-appearance-editor\n *ngSwitchCase=\"'dialogs'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n\n\n <json-config-editor\n *ngSwitchCase=\"'json'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n </ng-container>\n </div>\n </mat-tab>\n </mat-tab-group>\n<div class=\"config-editor-status\" *ngIf=\"statusMessage\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n</div>\n", styles: ["@charset \"UTF-8\";.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.educational-card{margin-bottom:24px;background:var(--mat-sys-surface-container-low);border-left:4px solid var(--mat-sys-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid rgba(0,0,0,.12);background:var(--mat-sys-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--mat-sys-error)}.status-text.success{color:var(--mat-sys-primary)}@media (max-width: 768px){.tab-content{padding:8px}.config-editor-status{padding:12px 16px}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}\n"] }]
19484
+ ], providers: [TableConfigService], template: " <mat-tab-group class=\"config-tabs\" [(selectedIndex)]=\"activeSectionIndex\">\n <mat-tab *ngFor=\"let section of sections\">\n <ng-template mat-tab-label>\n <mat-icon *ngIf=\"section.icon\" [praxisIcon]=\"section.icon\"></mat-icon>\n <span>{{ section.label }}</span>\n </ng-template>\n <div class=\"tab-content\">\n <ng-container [ngSwitch]=\"section.id\">\n <div *ngSwitchCase=\"'connect'\" style=\"display:grid; gap:12px; padding: 8px; grid-template-columns: 1fr 240px; align-items: start;\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Recurso</mat-label>\n <input matInput [(ngModel)]=\"resourcePath\" (ngModelChange)=\"onResourcePathChange($event)\" placeholder=\"ex.: employees\" />\n <mat-icon matSuffix>link</mat-icon>\n </mat-form-field>\n <mat-form-field appearance=\"outline\">\n <mat-label>Chave prim\u00E1ria</mat-label>\n <input matInput [(ngModel)]=\"idField\" (ngModelChange)=\"onIdFieldChange($event)\" placeholder=\"ex.: id, uuid, codigo\" />\n <mat-icon matSuffix>fingerprint</mat-icon>\n </mat-form-field>\n <small style=\"opacity:.75\">Defina o recurso da API e, se necess\u00E1rio, a chave prim\u00E1ria</small>\n\n <!-- Diverg\u00EAncia: idField -->\n <div *ngIf=\"idFieldDiverges\" style=\"grid-column: 1 / -1; padding: 8px 12px; border-radius: 6px; background: #fff4e5; color: #8a4b00; display:flex; align-items:center; gap: 8px;\">\n <mat-icon>warning</mat-icon>\n <div style=\"flex:1;\">\n A chave prim\u00E1ria no servidor diverge da configura\u00E7\u00E3o atual.\n <span style=\"opacity:.85\">(config: {{ idField || 'id' }} | servidor: {{ serverIdField || 'id' }})</span>\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onReconcileIdField()\">Reconciliar</button>\n </div>\n\n <!-- Diverg\u00EAncia: serverHash -->\n <div *ngIf=\"schemaHashDiverges\" style=\"grid-column: 1 / -1; padding: 8px 12px; border-radius: 6px; background: #e6f4ff; color: #0b5aaa; display:flex; align-items:center; gap: 8px;\">\n <mat-icon>info</mat-icon>\n <div style=\"flex:1;\">\n O schema no servidor foi atualizado desde a \u00FAltima sincroniza\u00E7\u00E3o.\n </div>\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"onAcceptServerHash()\">Atualizar metadados</button>\n </div>\n </div>\n\n <div *ngSwitchCase=\"'overview'\" class=\"overview-grid\">\n <div class=\"overview-row\">\n <mat-form-field appearance=\"outline\" class=\"hs-field\">\n <mat-label>Scroll Horizontal</mat-label>\n <mat-select [(ngModel)]=\"horizontalScroll\" (ngModelChange)=\"onHorizontalScrollChange($event)\">\n <mat-option value=\"auto\">Auto (padr\u00E3o)</mat-option>\n <mat-option value=\"wrap\">Wrap (quebrar linhas)</mat-option>\n <mat-option value=\"none\">Host controla</mat-option>\n </mat-select>\n </mat-form-field>\n <div class=\"help small\">Controla como a tabela lida com largura horizontal e barra de rolagem.</div>\n </div>\n <behavior-config-editor\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onBehaviorConfigChange($event)\"\n (behaviorChange)=\"onBehaviorChange($event)\"\n ></behavior-config-editor>\n </div>\n\n <columns-config-editor\n *ngSwitchCase=\"'columns'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n (columnChange)=\"onColumnChange($event)\"\n ></columns-config-editor>\n\n <table-rules-editor\n *ngSwitchCase=\"'rules'\"\n [config]=\"editedConfig\"\n [resourcePath]=\"resourcePath\"\n (configChange)=\"onRulesConfigChange($event)\"\n ></table-rules-editor>\n\n <header-appearance-editor\n *ngSwitchCase=\"'header'\"\n [config]=\"editedConfig\"\n (configChange)=\"onColumnsConfigChange($event)\"\n ></header-appearance-editor>\n\n <toolbar-actions-editor\n *ngSwitchCase=\"'toolbar'\"\n [config]=\"editedConfig\"\n (configChange)=\"onToolbarActionsConfigChange($event)\"\n (toolbarActionsChange)=\"onToolbarActionsChange($event)\"\n ></toolbar-actions-editor>\n\n <!-- Aba extra para integra\u00E7\u00F5es CRUD (vis\u00EDvel quando em contexto CRUD) -->\n <crud-integration-editor\n #crudEditorRef\n *ngSwitchCase=\"'crud'\"\n [tableId]=\"crudContext?.tableId || 'default'\"\n [crudContext]=\"crudContext\"\n ></crud-integration-editor>\n\n <filter-settings\n *ngSwitchCase=\"'filters'\"\n [metadata]=\"columnMetas\"\n [settings]=\"\n editedConfig.behavior?.filtering?.advancedFilters?.settings\n \"\n (settingsChange)=\"onFilterSettingsChange($event)\"\n ></filter-settings>\n\n <messages-localization-editor\n *ngSwitchCase=\"'messages'\"\n [config]=\"editedConfig\"\n (configChange)=\"onMessagesLocalizationConfigChange($event)\"\n (messagesLocalizationChange)=\"\n onMessagesLocalizationChange($event)\n \"\n ></messages-localization-editor>\n\n <confirm-dialog-appearance-editor\n *ngSwitchCase=\"'dialogs'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n ></confirm-dialog-appearance-editor>\n\n\n <json-config-editor\n *ngSwitchCase=\"'json'\"\n [config]=\"editedConfig\"\n (configChange)=\"onJsonConfigChange($event)\"\n (editorEvent)=\"onJsonEditorEvent($event)\"\n ></json-config-editor>\n </ng-container>\n </div>\n </mat-tab>\n </mat-tab-group>\n<div class=\"config-editor-status\" *ngIf=\"statusMessage\">\n <span\n class=\"status-text\"\n [class.error]=\"hasErrors\"\n [class.success]=\"hasSuccess\"\n >{{ statusMessage }}</span\n >\n</div>\n", styles: ["@charset \"UTF-8\";.config-tabs{flex:1 1 auto;display:flex;flex-direction:column}.config-tabs .mat-mdc-tab{min-width:120px}.config-tabs .mat-mdc-tab-body-wrapper,.config-tabs .mat-mdc-tab-group-container{flex:1 1 auto;min-height:0}.config-tabs .mat-mdc-tab-body-content{height:100%;min-height:0;overflow:visible}.tab-content{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding:8px 8px 56px;box-sizing:border-box;overflow:visible}.overview-grid{display:grid;grid-template-columns:1fr;gap:12px}.overview-grid .overview-row{display:grid;grid-template-columns:280px 1fr;align-items:end;gap:12px}.overview-grid .overview-row .help.small{opacity:.75;font-size:12px}.educational-card{margin-bottom:24px;background:var(--mat-sys-surface-container-low);border-left:4px solid var(--mat-sys-primary);flex-shrink:0}.config-editor-status{padding:12px 16px;margin-top:auto;border-top:1px solid rgba(0,0,0,.12);background:var(--mat-sys-surface-container);flex-shrink:0;position:relative}.status-text{font-size:.875rem;line-height:1.2}.status-text.error{color:var(--mat-sys-error)}.status-text.success{color:var(--mat-sys-primary)}@media (max-width: 768px){.tab-content{padding:8px}.config-editor-status{padding:12px 16px}:host{display:flex;flex-direction:column;flex:1 1 auto;min-height:0}}\n"] }]
19479
19485
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$3.TableConfigService }, { type: undefined, decorators: [{
19480
19486
  type: Inject,
19481
19487
  args: [SETTINGS_PANEL_DATA]
@@ -22812,9 +22818,23 @@ class PraxisTable {
22812
22818
  }
22813
22819
  catch { }
22814
22820
  }
22821
+ resolvePagingStrategy() {
22822
+ const raw = this.config.behavior?.pagination?.strategy;
22823
+ if (raw === 'client' || raw === 'server')
22824
+ return raw;
22825
+ // Default to server when using remote resource and not explicitly configured
22826
+ return this.resourcePath ? 'server' : 'client';
22827
+ }
22828
+ resolveSortingStrategy() {
22829
+ const raw = this.config.behavior?.sorting?.strategy;
22830
+ if (raw === 'client' || raw === 'server')
22831
+ return raw;
22832
+ // Default to server when using remote resource and not explicitly configured
22833
+ return this.resourcePath ? 'server' : 'client';
22834
+ }
22815
22835
  applyDataSourceSettings() {
22816
- const paginationStrategy = this.config.behavior?.pagination?.strategy ?? 'client';
22817
- const sortingStrategy = this.config.behavior?.sorting?.strategy ?? 'client';
22836
+ const paginationStrategy = this.resolvePagingStrategy();
22837
+ const sortingStrategy = this.resolveSortingStrategy();
22818
22838
  const isServerPaging = paginationStrategy === 'server';
22819
22839
  const isServerSorting = sortingStrategy === 'server';
22820
22840
  if (this.paginator) {
@@ -23023,7 +23043,7 @@ class PraxisTable {
23023
23043
  // Fall back to inference only if API type is not available or invalid
23024
23044
  apiType = this.inferFieldTypeFromFieldName(field.name);
23025
23045
  }
23026
- return {
23046
+ const col = {
23027
23047
  field: field.name,
23028
23048
  header: field.label ?? field.name,
23029
23049
  order: field.order,
@@ -23034,6 +23054,47 @@ class PraxisTable {
23034
23054
  _originalApiType: apiType,
23035
23055
  _isApiField: true,
23036
23056
  };
23057
+ // Auto-renderers: map known UI controls to richer cell renderers on first bootstrap
23058
+ // - Avatar: when backend marks controlType 'avatar' (x-ui) or field name matches avatar-like patterns
23059
+ try {
23060
+ this.applyAutoRenderer(field, col);
23061
+ }
23062
+ catch { }
23063
+ return col;
23064
+ }
23065
+ /**
23066
+ * Apply automatic renderer hints based on schema field metadata.
23067
+ * Runs only on initial bootstrap (when columns are derived from schema).
23068
+ */
23069
+ applyAutoRenderer(field, col) {
23070
+ if (!field || !col)
23071
+ return;
23072
+ // Do not override explicit renderer if any (defensive; initial columns have none)
23073
+ if (col.renderer && col.renderer.type)
23074
+ return;
23075
+ const ctl = (field.controlType || '').toString().trim().toLowerCase();
23076
+ const fname = (field.name || '').toString();
23077
+ const lname = fname.toLowerCase();
23078
+ const isAvatarByControl = ctl === 'avatar';
23079
+ if (isAvatarByControl) {
23080
+ // Derive initials from common name fields; leave altField undefined to avoid brittle coupling
23081
+ const initialsExpr = "= (row.nomeCompleto || row.nome || row.fullName || row.name || row.title || '').trim().split(/\\s+/).slice(0,2).map(p => p[0] || '').join('').toUpperCase()";
23082
+ col.renderer = {
23083
+ type: 'avatar',
23084
+ avatar: {
23085
+ srcField: fname,
23086
+ // altField intentionally omitted; rely on initialsExpr + title attr if provided later
23087
+ initialsExpr,
23088
+ shape: 'circle',
23089
+ size: 28,
23090
+ },
23091
+ };
23092
+ // Sensible defaults for avatar column
23093
+ col.align = col.align || 'center';
23094
+ if (!col.width)
23095
+ col.width = '56px';
23096
+ return;
23097
+ }
23037
23098
  }
23038
23099
  /**
23039
23100
  * Check if a value is a valid ColumnDataType
@@ -23170,6 +23231,13 @@ class PraxisTable {
23170
23231
  if (this.paginator) {
23171
23232
  this.paginator.length = page.totalElements;
23172
23233
  }
23234
+ // Keep config pagination length in sync for template bindings
23235
+ try {
23236
+ const beh = this.config.behavior || (this.config.behavior = {});
23237
+ const pag = beh.pagination || (beh.pagination = {});
23238
+ pag.totalItems = page.totalElements;
23239
+ }
23240
+ catch { }
23173
23241
  },
23174
23242
  error: (err) => {
23175
23243
  console.error('[PraxisTable] Data load error', err);