@praxisui/crud 10.0.0-rc.4 → 10.0.0-rc.6

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,31 +1,36 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, output, DestroyRef, ChangeDetectorRef, Injector, ViewChild, Output, Input, Optional, Inject, ViewEncapsulation, ENVIRONMENT_INITIALIZER } from '@angular/core';
2
+ import { EventEmitter, inject, ChangeDetectorRef, DestroyRef, Output, Input, ChangeDetectionStrategy, Component, Injectable, InjectionToken, input, signal, computed, effect, output, Injector, booleanAttribute, ViewChild, Optional, Inject, ViewEncapsulation, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import { HttpClient } from '@angular/common/http';
5
5
  import { Router, ActivatedRoute, RouterLink } from '@angular/router';
6
6
  import { MatSnackBar } from '@angular/material/snack-bar';
7
7
  import { firstValueFrom, BehaviorSubject, Subscription } from 'rxjs';
8
+ import { take, filter } from 'rxjs/operators';
8
9
  import * as i2$1 from '@praxisui/core';
9
- import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, LoggerService, SurfaceNavigationError, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, normalizeSurfaceOperationContext, translateUnavailableWorkflowMessage, isSurfaceNavigationError, translateSurfaceNavigationRejected, normalizeUnknownError, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, SURFACE_DRAWER_REF, SURFACE_DRAWER_CONTENT_DATA, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
10
+ import { SafeExpressionEvaluator, ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, LoggerService, SurfaceNavigationError, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, PraxisWidgetEventBus, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, normalizeSurfaceOperationContext, translateUnavailableWorkflowMessage, isSurfaceNavigationError, translateSurfaceNavigationRejected, normalizeUnknownError, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, SURFACE_DRAWER_REF, SURFACE_DRAWER_CONTENT_DATA, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
11
+ export { createDynamicRecord, extractRecordId, isDynamicDataRecord, resolveDataField } from '@praxisui/core';
10
12
  import { SettingsPanelService } from '@praxisui/settings-panel';
11
- import { PraxisTableInlineAuthoringEditorComponent, PraxisTable } from '@praxisui/table';
13
+ import { PraxisTableInlineAuthoringEditorComponent, PraxisTable, PraxisScopeBarComponent } from '@praxisui/table';
12
14
  import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
13
- import { take, filter } from 'rxjs/operators';
14
- import * as i1 from '@angular/material/dialog';
15
+ import * as i1 from '@angular/common';
16
+ import { CommonModule } from '@angular/common';
17
+ import * as i2 from '@angular/material/icon';
18
+ import { MatIconModule } from '@angular/material/icon';
19
+ import * as i3 from '@angular/material/button';
20
+ import { MatButtonModule } from '@angular/material/button';
21
+ import * as i4 from '@angular/material/tooltip';
22
+ import { MatTooltipModule } from '@angular/material/tooltip';
23
+ import * as i1$1 from '@angular/material/dialog';
15
24
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
16
25
  export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
17
- import * as i1$1 from '@angular/forms';
26
+ import * as i1$2 from '@angular/forms';
18
27
  import { FormsModule } from '@angular/forms';
19
- import * as i2 from '@angular/material/button';
20
- import { MatButtonModule } from '@angular/material/button';
21
- import * as i3 from '@angular/material/card';
28
+ import * as i3$1 from '@angular/material/card';
22
29
  import { MatCardModule } from '@angular/material/card';
23
- import * as i4 from '@angular/material/expansion';
30
+ import * as i4$1 from '@angular/material/expansion';
24
31
  import { MatExpansionModule } from '@angular/material/expansion';
25
32
  import * as i5 from '@angular/material/form-field';
26
33
  import { MatFormFieldModule } from '@angular/material/form-field';
27
- import * as i6 from '@angular/material/icon';
28
- import { MatIconModule } from '@angular/material/icon';
29
34
  import * as i7 from '@angular/material/input';
30
35
  import { MatInputModule } from '@angular/material/input';
31
36
  import * as i8 from '@angular/material/select';
@@ -36,6 +41,382 @@ import * as i10 from '@angular/material/tabs';
36
41
  import { MatTabsModule } from '@angular/material/tabs';
37
42
  import { PraxisDynamicForm } from '@praxisui/dynamic-form';
38
43
 
44
+ function formatKpiValue(value, format) {
45
+ if (value === null || value === undefined || value === '') {
46
+ return '—';
47
+ }
48
+ const num = Number(value);
49
+ const isNum = !isNaN(num) && typeof value !== 'boolean';
50
+ if (format === 'currency:compactBRL' || format === 'compactBRL') {
51
+ if (!isNum)
52
+ return String(value);
53
+ const sign = num < 0 ? '-' : '';
54
+ const abs = Math.abs(num);
55
+ if (abs >= 1_000_000_000) {
56
+ return `${sign}R$ ${(abs / 1_000_000_000).toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })}B`;
57
+ }
58
+ if (abs >= 1_000_000) {
59
+ return `${sign}R$ ${(abs / 1_000_000).toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })}M`;
60
+ }
61
+ if (abs >= 1_000) {
62
+ return `${sign}R$ ${(abs / 1_000).toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 1 })}K`;
63
+ }
64
+ return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(num);
65
+ }
66
+ if (format === 'currency:BRL' || format === 'currency') {
67
+ if (!isNum)
68
+ return String(value);
69
+ return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(num);
70
+ }
71
+ if (format === 'percent' || format === 'percentage') {
72
+ if (!isNum)
73
+ return String(value);
74
+ return `${num}%`;
75
+ }
76
+ if (format === 'number:compact' || format === 'compact') {
77
+ if (!isNum)
78
+ return String(value);
79
+ const sign = num < 0 ? '-' : '';
80
+ const abs = Math.abs(num);
81
+ if (abs >= 1_000_000_000) {
82
+ return `${sign}${(abs / 1_000_000_000).toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })}B`;
83
+ }
84
+ if (abs >= 1_000_000) {
85
+ return `${sign}${(abs / 1_000_000).toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })}M`;
86
+ }
87
+ if (abs >= 1_000) {
88
+ return `${sign}${(abs / 1_000).toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 1 })}K`;
89
+ }
90
+ return num.toLocaleString('pt-BR');
91
+ }
92
+ if (format === 'number') {
93
+ if (!isNum)
94
+ return String(value);
95
+ return num.toLocaleString('pt-BR');
96
+ }
97
+ return String(value);
98
+ }
99
+ class PraxisKpiBandComponent {
100
+ config;
101
+ dataContext;
102
+ /**
103
+ * Indica se o container hospedeiro (ex.: PraxisCrudComponent) possui ouvintes ativos
104
+ * para cliques em cards. Quando explicitamente falso, evita a falsa affordance de botões.
105
+ */
106
+ isInteractiveHost;
107
+ cardClick = new EventEmitter();
108
+ statsLoaded = new EventEmitter();
109
+ statsError = new EventEmitter();
110
+ refreshClick = new EventEmitter();
111
+ http = inject(HttpClient);
112
+ cdr = inject(ChangeDetectorRef);
113
+ destroyRef = inject(DestroyRef);
114
+ evaluator = new SafeExpressionEvaluator({
115
+ maxLength: 500,
116
+ maxTokens: 150,
117
+ });
118
+ isLoading = false;
119
+ evaluatedCards = [];
120
+ fetchedData = {};
121
+ skeletonPlaceholders = [1, 2, 3, 4];
122
+ get isVisible() {
123
+ if (!this.config || this.config.enabled === false)
124
+ return false;
125
+ return !!(this.config.cards?.length || this.config.source || this.config.document);
126
+ }
127
+ get resolvedColumns() {
128
+ if (this.config?.columns && this.config.columns > 0) {
129
+ return String(this.config.columns);
130
+ }
131
+ return 'auto-fit';
132
+ }
133
+ get resolvedGap() {
134
+ const gap = this.config?.gap;
135
+ if (gap === 'sm')
136
+ return '0.5rem';
137
+ if (gap === 'md')
138
+ return '1rem';
139
+ if (gap === 'lg')
140
+ return '1.5rem';
141
+ return gap || '1rem';
142
+ }
143
+ ngOnInit() {
144
+ if (this.config?.cards?.length) {
145
+ this.skeletonPlaceholders = Array.from({ length: this.config.cards.length }, (_, i) => i + 1);
146
+ }
147
+ this.refresh();
148
+ }
149
+ ngOnChanges(changes) {
150
+ if (changes['config'] || changes['dataContext']) {
151
+ if (changes['config'] && this.config?.source !== changes['config'].previousValue?.source) {
152
+ this.refresh();
153
+ }
154
+ else {
155
+ this.recomputeCards();
156
+ }
157
+ }
158
+ }
159
+ handleRefresh() {
160
+ this.refreshClick.emit();
161
+ this.refresh();
162
+ }
163
+ refresh() {
164
+ if (this.config?.source) {
165
+ this.fetchFromSource();
166
+ }
167
+ else {
168
+ this.recomputeCards();
169
+ }
170
+ }
171
+ fetchFromSource() {
172
+ const source = this.config?.source;
173
+ if (!source)
174
+ return;
175
+ this.isLoading = true;
176
+ this.cdr.markForCheck();
177
+ const method = this.config?.method || 'GET';
178
+ const request$ = method.toUpperCase() === 'POST'
179
+ ? this.http.post(source, this.config?.payload || {})
180
+ : this.http.get(source);
181
+ request$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
182
+ next: (res) => {
183
+ const payload = res?.data ?? res ?? {};
184
+ this.fetchedData = typeof payload === 'object' ? payload : { value: payload };
185
+ this.isLoading = false;
186
+ this.statsLoaded.emit(this.fetchedData);
187
+ this.recomputeCards();
188
+ this.cdr.markForCheck();
189
+ },
190
+ error: (err) => {
191
+ this.isLoading = false;
192
+ this.statsError.emit(err);
193
+ this.recomputeCards();
194
+ this.cdr.markForCheck();
195
+ },
196
+ });
197
+ }
198
+ recomputeCards() {
199
+ const cards = this.config?.cards;
200
+ if (!cards || !Array.isArray(cards) || cards.length === 0) {
201
+ this.evaluatedCards = [];
202
+ this.cdr.markForCheck();
203
+ return;
204
+ }
205
+ const context = {
206
+ ...(this.dataContext || {}),
207
+ ...(this.fetchedData || {}),
208
+ stats: this.fetchedData,
209
+ data: this.fetchedData,
210
+ };
211
+ const isObserved = this.isInteractiveHost !== undefined ? this.isInteractiveHost : this.cardClick.observed;
212
+ this.evaluatedCards = cards.map((card) => {
213
+ let rawValue = card.value;
214
+ if (card.valueExpr) {
215
+ const res = this.evaluator.evaluate(card.valueExpr, context);
216
+ if (!res.error && res.value !== undefined) {
217
+ rawValue = res.value;
218
+ }
219
+ }
220
+ let rawCaption = card.caption;
221
+ if (card.captionExpr) {
222
+ const res = this.evaluator.evaluate(card.captionExpr, context);
223
+ if (!res.error && res.value !== undefined) {
224
+ rawCaption = String(res.value);
225
+ }
226
+ }
227
+ const displayValue = formatKpiValue(rawValue, card.format);
228
+ const interactive = card.interactive !== undefined
229
+ ? card.interactive
230
+ : !!(card.action || card.filter || card.route || isObserved);
231
+ return {
232
+ raw: card,
233
+ id: card.id,
234
+ label: card.label,
235
+ displayValue,
236
+ displayCaption: rawCaption,
237
+ footnote: card.footnote,
238
+ icon: card.icon,
239
+ tone: card.tone || 'neutral',
240
+ interactive,
241
+ className: card.className,
242
+ tooltip: card.tooltip,
243
+ };
244
+ });
245
+ this.cdr.markForCheck();
246
+ }
247
+ onCardClick(card, index) {
248
+ if (!card.interactive)
249
+ return;
250
+ this.cardClick.emit({
251
+ card: card.raw,
252
+ index,
253
+ data: this.fetchedData,
254
+ });
255
+ }
256
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisKpiBandComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
257
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisKpiBandComponent, isStandalone: true, selector: "praxis-kpi-band", inputs: { config: "config", dataContext: "dataContext", isInteractiveHost: "isInteractiveHost" }, outputs: { cardClick: "cardClick", statsLoaded: "statsLoaded", statsError: "statsError", refreshClick: "refreshClick" }, usesOnChanges: true, ngImport: i0, template: `
258
+ <div
259
+ *ngIf="isVisible"
260
+ class="praxis-kpi-band"
261
+ [class]="config?.className || ''"
262
+ [style.--praxis-kpi-columns]="resolvedColumns"
263
+ [style.--praxis-kpi-gap]="resolvedGap"
264
+ >
265
+ <div *ngIf="config?.title || config?.showRefresh" class="praxis-kpi-band-header">
266
+ <div class="praxis-kpi-band-title-group">
267
+ <h3 *ngIf="config?.title" class="praxis-kpi-band-title">{{ config?.title }}</h3>
268
+ <p *ngIf="config?.subtitle" class="praxis-kpi-band-subtitle">{{ config?.subtitle }}</p>
269
+ </div>
270
+ <div *ngIf="config?.showRefresh" class="praxis-kpi-band-actions">
271
+ <button
272
+ mat-icon-button
273
+ type="button"
274
+ class="praxis-kpi-refresh-button"
275
+ (click)="handleRefresh()"
276
+ matTooltip="Atualizar indicadores"
277
+ >
278
+ <mat-icon>refresh</mat-icon>
279
+ </button>
280
+ </div>
281
+ </div>
282
+
283
+ <div class="praxis-kpi-band-grid">
284
+ <ng-container *ngIf="isLoading; else cardsTpl">
285
+ <div
286
+ *ngFor="let placeholder of skeletonPlaceholders"
287
+ class="praxis-kpi-card praxis-kpi-card-skeleton"
288
+ >
289
+ <div class="praxis-kpi-skeleton-line label"></div>
290
+ <div class="praxis-kpi-skeleton-line value"></div>
291
+ <div class="praxis-kpi-skeleton-line caption"></div>
292
+ </div>
293
+ </ng-container>
294
+
295
+ <ng-template #cardsTpl>
296
+ <div
297
+ *ngFor="let card of evaluatedCards; let i = index"
298
+ class="praxis-kpi-card"
299
+ [class.interactive]="card.interactive"
300
+ [ngClass]="[card.tone ? 'tone-' + card.tone : 'tone-neutral', card.className || '']"
301
+ [attr.role]="card.interactive ? 'button' : null"
302
+ [attr.tabindex]="card.interactive ? 0 : null"
303
+ [matTooltip]="card.tooltip || ''"
304
+ (click)="onCardClick(card, i)"
305
+ (keydown.enter)="onCardClick(card, i)"
306
+ (keydown.space)="$event.preventDefault(); onCardClick(card, i)"
307
+ >
308
+ <div class="praxis-kpi-card-top">
309
+ <span class="praxis-kpi-card-label">{{ card.label }}</span>
310
+ <div *ngIf="card.icon" class="praxis-kpi-card-icon">
311
+ <mat-icon>{{ card.icon }}</mat-icon>
312
+ </div>
313
+ </div>
314
+
315
+ <div class="praxis-kpi-card-value">{{ card.displayValue }}</div>
316
+
317
+ <div *ngIf="card.displayCaption" class="praxis-kpi-card-caption">
318
+ {{ card.displayCaption }}
319
+ </div>
320
+
321
+ <div *ngIf="card.footnote" class="praxis-kpi-card-footnote">
322
+ {{ card.footnote }}
323
+ </div>
324
+ </div>
325
+ </ng-template>
326
+ </div>
327
+ </div>
328
+ `, isInline: true, styles: [".praxis-kpi-band{display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;width:100%;box-sizing:border-box}.praxis-kpi-band-header{display:flex;align-items:center;justify-content:space-between;gap:1rem}.praxis-kpi-band-title{margin:0;font-size:1rem;font-weight:600;color:var(--praxis-text-primary, #0f172a)}.praxis-kpi-band-subtitle{margin:.125rem 0 0;font-size:.8125rem;color:var(--praxis-text-muted, #64748b)}.praxis-kpi-band-grid{display:grid;grid-template-columns:repeat(var(--praxis-kpi-columns, auto-fit),minmax(200px,1fr));gap:var(--praxis-kpi-gap, 1rem);width:100%}.praxis-kpi-card{display:flex;flex-direction:column;justify-content:space-between;padding:1rem 1.25rem;border-radius:.75rem;background:var(--praxis-surface-card, #ffffff);border:1px solid var(--praxis-border-subtle, #e2e8f0);box-shadow:0 1px 3px #0000000a;transition:transform .15s ease,box-shadow .15s ease,border-color .15s ease;position:relative;overflow:hidden;min-height:5.5rem;box-sizing:border-box}.praxis-kpi-card.interactive{cursor:pointer}.praxis-kpi-card.interactive:hover{transform:translateY(-2px);box-shadow:0 4px 12px #00000014}.praxis-kpi-card.interactive:focus-visible{outline:2px solid var(--praxis-primary, #0284c7);outline-offset:2px}.praxis-kpi-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem}.praxis-kpi-card-label{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--praxis-text-muted, #64748b)}.praxis-kpi-card-icon{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:.5rem}.praxis-kpi-card-icon mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.praxis-kpi-card-value{font-size:1.625rem;font-weight:700;line-height:1.2;letter-spacing:-.02em;font-feature-settings:\"tnum\" on,\"lnum\" on;color:var(--praxis-text-primary, #0f172a)}.praxis-kpi-card-caption{font-size:.75rem;color:var(--praxis-text-muted, #64748b);margin-top:.375rem}.praxis-kpi-card-footnote{font-size:.7rem;color:var(--praxis-text-secondary, #94a3b8);margin-top:.25rem}.tone-info{border-left:4px solid var(--praxis-info, #0284c7)}.tone-info .praxis-kpi-card-icon{background:#f0f9ff;color:#0284c7}.tone-success{border-left:4px solid var(--praxis-success, #059669)}.tone-success .praxis-kpi-card-icon{background:#ecfdf5;color:#059669}.tone-warning{border-left:4px solid var(--praxis-warning, #d97706)}.tone-warning .praxis-kpi-card-icon{background:#fffbeb;color:#d97706}.tone-error,.tone-danger{border-left:4px solid var(--praxis-error, #dc2626)}.tone-error .praxis-kpi-card-icon,.tone-danger .praxis-kpi-card-icon{background:#fef2f2;color:#dc2626}.tone-neutral{border-left:4px solid var(--praxis-neutral, #94a3b8)}.tone-neutral .praxis-kpi-card-icon{background:#f8fafc;color:#64748b}.praxis-kpi-card-skeleton{pointer-events:none}.praxis-kpi-skeleton-line{background:linear-gradient(90deg,#f1f5f9 25%,#e2e8f0,#f1f5f9 75%);background-size:200% 100%;animation:praxis-shimmer 1.5s infinite;border-radius:.25rem}.praxis-kpi-skeleton-line.label{width:40%;height:.75rem;margin-bottom:.75rem}.praxis-kpi-skeleton-line.value{width:65%;height:1.5rem;margin-bottom:.5rem}.praxis-kpi-skeleton-line.caption{width:50%;height:.65rem}@keyframes praxis-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
329
+ }
330
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisKpiBandComponent, decorators: [{
331
+ type: Component,
332
+ args: [{ selector: 'praxis-kpi-band', standalone: true, imports: [CommonModule, MatIconModule, MatButtonModule, MatTooltipModule], template: `
333
+ <div
334
+ *ngIf="isVisible"
335
+ class="praxis-kpi-band"
336
+ [class]="config?.className || ''"
337
+ [style.--praxis-kpi-columns]="resolvedColumns"
338
+ [style.--praxis-kpi-gap]="resolvedGap"
339
+ >
340
+ <div *ngIf="config?.title || config?.showRefresh" class="praxis-kpi-band-header">
341
+ <div class="praxis-kpi-band-title-group">
342
+ <h3 *ngIf="config?.title" class="praxis-kpi-band-title">{{ config?.title }}</h3>
343
+ <p *ngIf="config?.subtitle" class="praxis-kpi-band-subtitle">{{ config?.subtitle }}</p>
344
+ </div>
345
+ <div *ngIf="config?.showRefresh" class="praxis-kpi-band-actions">
346
+ <button
347
+ mat-icon-button
348
+ type="button"
349
+ class="praxis-kpi-refresh-button"
350
+ (click)="handleRefresh()"
351
+ matTooltip="Atualizar indicadores"
352
+ >
353
+ <mat-icon>refresh</mat-icon>
354
+ </button>
355
+ </div>
356
+ </div>
357
+
358
+ <div class="praxis-kpi-band-grid">
359
+ <ng-container *ngIf="isLoading; else cardsTpl">
360
+ <div
361
+ *ngFor="let placeholder of skeletonPlaceholders"
362
+ class="praxis-kpi-card praxis-kpi-card-skeleton"
363
+ >
364
+ <div class="praxis-kpi-skeleton-line label"></div>
365
+ <div class="praxis-kpi-skeleton-line value"></div>
366
+ <div class="praxis-kpi-skeleton-line caption"></div>
367
+ </div>
368
+ </ng-container>
369
+
370
+ <ng-template #cardsTpl>
371
+ <div
372
+ *ngFor="let card of evaluatedCards; let i = index"
373
+ class="praxis-kpi-card"
374
+ [class.interactive]="card.interactive"
375
+ [ngClass]="[card.tone ? 'tone-' + card.tone : 'tone-neutral', card.className || '']"
376
+ [attr.role]="card.interactive ? 'button' : null"
377
+ [attr.tabindex]="card.interactive ? 0 : null"
378
+ [matTooltip]="card.tooltip || ''"
379
+ (click)="onCardClick(card, i)"
380
+ (keydown.enter)="onCardClick(card, i)"
381
+ (keydown.space)="$event.preventDefault(); onCardClick(card, i)"
382
+ >
383
+ <div class="praxis-kpi-card-top">
384
+ <span class="praxis-kpi-card-label">{{ card.label }}</span>
385
+ <div *ngIf="card.icon" class="praxis-kpi-card-icon">
386
+ <mat-icon>{{ card.icon }}</mat-icon>
387
+ </div>
388
+ </div>
389
+
390
+ <div class="praxis-kpi-card-value">{{ card.displayValue }}</div>
391
+
392
+ <div *ngIf="card.displayCaption" class="praxis-kpi-card-caption">
393
+ {{ card.displayCaption }}
394
+ </div>
395
+
396
+ <div *ngIf="card.footnote" class="praxis-kpi-card-footnote">
397
+ {{ card.footnote }}
398
+ </div>
399
+ </div>
400
+ </ng-template>
401
+ </div>
402
+ </div>
403
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-kpi-band{display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;width:100%;box-sizing:border-box}.praxis-kpi-band-header{display:flex;align-items:center;justify-content:space-between;gap:1rem}.praxis-kpi-band-title{margin:0;font-size:1rem;font-weight:600;color:var(--praxis-text-primary, #0f172a)}.praxis-kpi-band-subtitle{margin:.125rem 0 0;font-size:.8125rem;color:var(--praxis-text-muted, #64748b)}.praxis-kpi-band-grid{display:grid;grid-template-columns:repeat(var(--praxis-kpi-columns, auto-fit),minmax(200px,1fr));gap:var(--praxis-kpi-gap, 1rem);width:100%}.praxis-kpi-card{display:flex;flex-direction:column;justify-content:space-between;padding:1rem 1.25rem;border-radius:.75rem;background:var(--praxis-surface-card, #ffffff);border:1px solid var(--praxis-border-subtle, #e2e8f0);box-shadow:0 1px 3px #0000000a;transition:transform .15s ease,box-shadow .15s ease,border-color .15s ease;position:relative;overflow:hidden;min-height:5.5rem;box-sizing:border-box}.praxis-kpi-card.interactive{cursor:pointer}.praxis-kpi-card.interactive:hover{transform:translateY(-2px);box-shadow:0 4px 12px #00000014}.praxis-kpi-card.interactive:focus-visible{outline:2px solid var(--praxis-primary, #0284c7);outline-offset:2px}.praxis-kpi-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:.5rem}.praxis-kpi-card-label{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--praxis-text-muted, #64748b)}.praxis-kpi-card-icon{display:flex;align-items:center;justify-content:center;width:2rem;height:2rem;border-radius:.5rem}.praxis-kpi-card-icon mat-icon{font-size:1.25rem;width:1.25rem;height:1.25rem}.praxis-kpi-card-value{font-size:1.625rem;font-weight:700;line-height:1.2;letter-spacing:-.02em;font-feature-settings:\"tnum\" on,\"lnum\" on;color:var(--praxis-text-primary, #0f172a)}.praxis-kpi-card-caption{font-size:.75rem;color:var(--praxis-text-muted, #64748b);margin-top:.375rem}.praxis-kpi-card-footnote{font-size:.7rem;color:var(--praxis-text-secondary, #94a3b8);margin-top:.25rem}.tone-info{border-left:4px solid var(--praxis-info, #0284c7)}.tone-info .praxis-kpi-card-icon{background:#f0f9ff;color:#0284c7}.tone-success{border-left:4px solid var(--praxis-success, #059669)}.tone-success .praxis-kpi-card-icon{background:#ecfdf5;color:#059669}.tone-warning{border-left:4px solid var(--praxis-warning, #d97706)}.tone-warning .praxis-kpi-card-icon{background:#fffbeb;color:#d97706}.tone-error,.tone-danger{border-left:4px solid var(--praxis-error, #dc2626)}.tone-error .praxis-kpi-card-icon,.tone-danger .praxis-kpi-card-icon{background:#fef2f2;color:#dc2626}.tone-neutral{border-left:4px solid var(--praxis-neutral, #94a3b8)}.tone-neutral .praxis-kpi-card-icon{background:#f8fafc;color:#64748b}.praxis-kpi-card-skeleton{pointer-events:none}.praxis-kpi-skeleton-line{background:linear-gradient(90deg,#f1f5f9 25%,#e2e8f0,#f1f5f9 75%);background-size:200% 100%;animation:praxis-shimmer 1.5s infinite;border-radius:.25rem}.praxis-kpi-skeleton-line.label{width:40%;height:.75rem;margin-bottom:.75rem}.praxis-kpi-skeleton-line.value{width:65%;height:1.5rem;margin-bottom:.5rem}.praxis-kpi-skeleton-line.caption{width:50%;height:.65rem}@keyframes praxis-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
404
+ }], propDecorators: { config: [{
405
+ type: Input
406
+ }], dataContext: [{
407
+ type: Input
408
+ }], isInteractiveHost: [{
409
+ type: Input
410
+ }], cardClick: [{
411
+ type: Output
412
+ }], statsLoaded: [{
413
+ type: Output
414
+ }], statsError: [{
415
+ type: Output
416
+ }], refreshClick: [{
417
+ type: Output
418
+ }] } });
419
+
39
420
  class DialogService {
40
421
  matDialog;
41
422
  zone;
@@ -50,13 +431,13 @@ class DialogService {
50
431
  const component = await loader();
51
432
  return this.zone.run(() => this.matDialog.open(component, config));
52
433
  }
53
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, deps: [{ token: i1.MatDialog }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
434
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, deps: [{ token: i1$1.MatDialog }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable });
54
435
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, providedIn: 'root' });
55
436
  }
56
437
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DialogService, decorators: [{
57
438
  type: Injectable,
58
439
  args: [{ providedIn: 'root' }]
59
- }], ctorParameters: () => [{ type: i1.MatDialog }, { type: i0.NgZone }] });
440
+ }], ctorParameters: () => [{ type: i1$1.MatDialog }, { type: i0.NgZone }] });
60
441
 
61
442
  function getCrudDrawerAdapterToken() {
62
443
  const registryKey = '__PAX_CRUD_DRAWER_ADAPTER_TOKEN__';
@@ -3280,7 +3661,7 @@ class CrudMetadataEditorComponent {
3280
3661
  </mat-tab>
3281
3662
  </mat-tab-group>
3282
3663
  </section>
3283
- `, 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}.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: 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: MatButtonModule }, { kind: "component", type: i2.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.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.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: i8.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: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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: i10.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i10.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i10.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: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "readonly"], outputs: ["configChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3664
+ `, 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}.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: i1$2.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$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.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$1.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$1.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$1.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i4$1.MatExpansionPanelDescription, selector: "mat-panel-description" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.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: i8.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: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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: i10.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i10.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i10.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: PraxisTableInlineAuthoringEditorComponent, selector: "praxis-table-inline-authoring-editor", inputs: ["config", "readonly"], outputs: ["configChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3284
3665
  }
3285
3666
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudMetadataEditorComponent, decorators: [{
3286
3667
  type: Component,
@@ -3938,7 +4319,32 @@ function isOperationalRenderer(renderer) {
3938
4319
 
3939
4320
  class PraxisCrudComponent {
3940
4321
  metadata;
3941
- crudId;
4322
+ crudId = 'praxis-crud';
4323
+ _resource;
4324
+ _autoGeneratedMetadata = false;
4325
+ /** Alias canônico para descoberta integral do recurso (Issue #38) */
4326
+ set resource(value) {
4327
+ this._resource = value;
4328
+ if (value && (!this.metadata || this._autoGeneratedMetadata)) {
4329
+ this._autoGeneratedMetadata = true;
4330
+ this.metadata = {
4331
+ component: 'praxis-crud',
4332
+ resource: {
4333
+ path: value,
4334
+ idField: 'id',
4335
+ },
4336
+ table: {
4337
+ columns: [],
4338
+ },
4339
+ };
4340
+ if (this.crudId === 'praxis-crud' || !this.crudId || this.crudId.startsWith('crud-')) {
4341
+ this.crudId = `crud-${value.replace(/\//g, '-')}`;
4342
+ }
4343
+ }
4344
+ }
4345
+ get resource() {
4346
+ return this._resource;
4347
+ }
3942
4348
  componentInstanceId;
3943
4349
  /** Controls whether the CRUD's internal table trusts host input or stored user configuration. */
3944
4350
  tableConfigPersistenceStrategy = 'local-first';
@@ -3948,6 +4354,24 @@ class PraxisCrudComponent {
3948
4354
  showResourceIdentityKey = true;
3949
4355
  /** Capability publica exigida para authoring governado do CRUD e da tabela interna. */
3950
4356
  authoringCapability = null;
4357
+ /** Critérios reativos de filtro aplicados à tabela interna (Issue #11) */
4358
+ filterCriteria;
4359
+ /** Habilita teleporte da barra de filtros para container externo (Issue #11) */
4360
+ filterTeleport = false;
4361
+ /** Desabilita auto-abertura da gaveta analítica no clique de linha (Issue #39) */
4362
+ disableAutoDrawer = false;
4363
+ get filterTemplate() {
4364
+ return this.table?.filterTemplate;
4365
+ }
4366
+ get internalTable() {
4367
+ return this.table;
4368
+ }
4369
+ openAnalyticalDrawer(row, tabId) {
4370
+ this.table?.openAnalyticalDrawer(row, tabId);
4371
+ }
4372
+ closeAnalyticalDrawer() {
4373
+ this.table?.closeAnalyticalDrawer();
4374
+ }
3951
4375
  configureRequested = new EventEmitter();
3952
4376
  afterOpen = new EventEmitter();
3953
4377
  afterClose = new EventEmitter();
@@ -3965,8 +4389,39 @@ class PraxisCrudComponent {
3965
4389
  /** Forwards the canonical resource interaction envelope emitted by the internal table. */
3966
4390
  resourceEvent = output();
3967
4391
  tableRuntimeConfigChange = new EventEmitter();
4392
+ drawerOpen = new EventEmitter();
4393
+ drawerClose = new EventEmitter();
3968
4394
  crudAuthoringDocumentApplied = new EventEmitter();
3969
4395
  crudAuthoringDocumentSaved = new EventEmitter();
4396
+ /** Evento emitido ao clicar em um card da faixa declarativa de KPIs (Issue #33) */
4397
+ kpiCardClick = new EventEmitter();
4398
+ get resolvedKpiBandConfig() {
4399
+ const meta = this.resolvedMetadata;
4400
+ const band = meta?.kpiBand || meta?.table?.kpiBand || meta?.table?.behavior?.kpiBand;
4401
+ if (band && band.enabled !== false && (band.cards?.length || band.source || band.document)) {
4402
+ return band;
4403
+ }
4404
+ return null;
4405
+ }
4406
+ get isKpiInteractiveHost() {
4407
+ return this.kpiCardClick.observed;
4408
+ }
4409
+ get kpiDataContext() {
4410
+ return {
4411
+ ...(this.context || {}),
4412
+ data: this.resolvedMetadata?.data,
4413
+ };
4414
+ }
4415
+ onKpiCardClick(event) {
4416
+ this.kpiCardClick.emit(event);
4417
+ if (event.card.filter && typeof event.card.filter === 'object') {
4418
+ this.tableQueryContext = {
4419
+ ...(this.tableQueryContext || {}),
4420
+ ...event.card.filter,
4421
+ };
4422
+ this.cdr.markForCheck();
4423
+ }
4424
+ }
3970
4425
  resolvedMetadata;
3971
4426
  effectiveTableConfig;
3972
4427
  tableConfigForBinding = createDefaultTableConfig();
@@ -3978,6 +4433,7 @@ class PraxisCrudComponent {
3978
4433
  http = inject(HttpClient);
3979
4434
  destroyRef = inject(DestroyRef);
3980
4435
  cdr = inject(ChangeDetectorRef);
4436
+ widgetEventBus = inject(PraxisWidgetEventBus, { optional: true });
3981
4437
  table;
3982
4438
  storage = inject(ASYNC_CONFIG_STORAGE);
3983
4439
  settingsPanel = inject(SettingsPanelService);
@@ -4030,6 +4486,25 @@ class PraxisCrudComponent {
4030
4486
  }
4031
4487
  this.cdr.markForCheck();
4032
4488
  });
4489
+ if (this.widgetEventBus) {
4490
+ this.widgetEventBus
4491
+ .onFilter()
4492
+ .pipe(filter((evt) => {
4493
+ if (!evt.targetWidgetId || evt.targetWidgetId === '*') {
4494
+ return true;
4495
+ }
4496
+ return (evt.targetWidgetId === this.crudId ||
4497
+ (!!this.resource && evt.targetWidgetId === this.resource));
4498
+ }), takeUntilDestroyed(this.destroyRef))
4499
+ .subscribe((evt) => {
4500
+ if (evt.eventType === 'filter.apply') {
4501
+ this.applyFilterCriteria(evt.payload);
4502
+ }
4503
+ else if (evt.eventType === 'filter.clear') {
4504
+ this.clearFilterCriteria();
4505
+ }
4506
+ });
4507
+ }
4033
4508
  }
4034
4509
  collectionCapabilitiesRequestHref = null;
4035
4510
  collectionCapabilitiesResolvedHref = null;
@@ -4091,6 +4566,12 @@ class PraxisCrudComponent {
4091
4566
  }
4092
4567
  this.resourceEvent.emit(event);
4093
4568
  }
4569
+ onTableDrawerOpen(event) {
4570
+ this.drawerOpen.emit(event);
4571
+ }
4572
+ onTableDrawerClose() {
4573
+ this.drawerClose.emit();
4574
+ }
4094
4575
  async onBulkAction(event) {
4095
4576
  if (this.isInspectOnly()) {
4096
4577
  return;
@@ -4118,16 +4599,72 @@ class PraxisCrudComponent {
4118
4599
  await this.onAction(action, row ?? undefined, event);
4119
4600
  }
4120
4601
  ngOnChanges(changes) {
4121
- if (!changes['metadata'] && !changes['context']) {
4602
+ if (!changes['metadata'] && !changes['context'] && !changes['resource']) {
4122
4603
  return;
4123
4604
  }
4605
+ if (changes['metadata']) {
4606
+ this._autoGeneratedMetadata = false;
4607
+ }
4608
+ if (changes['resource'] && !changes['metadata'] && this.resource) {
4609
+ this._autoGeneratedMetadata = true;
4610
+ this.metadata = {
4611
+ component: 'praxis-crud',
4612
+ resource: {
4613
+ path: this.resource,
4614
+ idField: 'id',
4615
+ },
4616
+ table: {
4617
+ columns: [],
4618
+ },
4619
+ };
4620
+ if (this.crudId === 'praxis-crud' || !this.crudId || this.crudId.startsWith('crud-')) {
4621
+ this.crudId = `crud-${this.resource.replace(/\//g, '-')}`;
4622
+ }
4623
+ }
4124
4624
  this.materializedRefreshSequence += 1;
4125
4625
  this.lastTableRuntimeConfigSnapshot = null;
4126
4626
  this.lastEmittedTableRuntimeConfigJson = '';
4627
+ if (changes['filterCriteria'] && !changes['filterCriteria'].firstChange) {
4628
+ this.applyFilterCriteria(this.filterCriteria);
4629
+ }
4127
4630
  try {
4128
- const parsed = typeof this.metadata === 'string'
4129
- ? JSON.parse(this.metadata)
4130
- : this.metadata;
4631
+ let parsed;
4632
+ if (typeof this.metadata === 'string') {
4633
+ const trimmed = this.metadata.trim();
4634
+ if (trimmed.startsWith('{')) {
4635
+ parsed = JSON.parse(trimmed);
4636
+ }
4637
+ else {
4638
+ parsed = {
4639
+ component: 'praxis-crud',
4640
+ resource: {
4641
+ path: trimmed,
4642
+ idField: 'id',
4643
+ },
4644
+ table: {
4645
+ columns: [],
4646
+ },
4647
+ };
4648
+ }
4649
+ }
4650
+ else if (this.metadata) {
4651
+ parsed = this.metadata;
4652
+ }
4653
+ else if (this.resource) {
4654
+ parsed = {
4655
+ component: 'praxis-crud',
4656
+ resource: {
4657
+ path: this.resource,
4658
+ idField: 'id',
4659
+ },
4660
+ table: {
4661
+ columns: [],
4662
+ },
4663
+ };
4664
+ }
4665
+ else {
4666
+ return;
4667
+ }
4131
4668
  this.resolvedMetadata = parsed;
4132
4669
  const externalAuthoringSeed = parsed.interactionMode !== 'inspect-only' && this.context && typeof this.context === 'object'
4133
4670
  ? (this.context['authoringDocument'] ?? this.context['authoringMetadata'])
@@ -4723,14 +5260,39 @@ class PraxisCrudComponent {
4723
5260
  return this.isRecord(meta?.queryContext) ? meta.queryContext : null;
4724
5261
  }
4725
5262
  resolveFilterCriteria(meta) {
4726
- return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
5263
+ const fromInput = this.isRecord(this.filterCriteria) ? { ...this.filterCriteria } : {};
5264
+ const fromMeta = this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
5265
+ return { ...fromMeta, ...fromInput };
5266
+ }
5267
+ /**
5268
+ * Aplica critérios de busca avançada na tabela interna do CRUD (Issue #11).
5269
+ */
5270
+ applyFilterCriteria(criteria) {
5271
+ this.tableFilterCriteria = {
5272
+ ...this.resolveFilterCriteria(this.resolvedMetadata),
5273
+ ...(criteria || {}),
5274
+ };
5275
+ if (this.table) {
5276
+ this.table.applyAdvancedFilterCriteria(this.tableFilterCriteria);
5277
+ }
5278
+ this.cdr.markForCheck();
5279
+ }
5280
+ /**
5281
+ * Limpa os filtros ativos na tabela interna do CRUD (Issue #11).
5282
+ */
5283
+ clearFilterCriteria() {
5284
+ this.tableFilterCriteria = {};
5285
+ if (this.table) {
5286
+ this.table.clearAdvancedFilter();
5287
+ }
5288
+ this.cdr.markForCheck();
4727
5289
  }
4728
5290
  extractRowFromEvent(event) {
4729
5291
  if (!this.isRecord(event)) {
4730
5292
  return null;
4731
5293
  }
4732
5294
  const row = event['row'];
4733
- return this.isRecord(row) ? row : null;
5295
+ return this.isRecord(row) ? row : (this.isRecord(event) ? event : null);
4734
5296
  }
4735
5297
  extractSelectedRowFromEvent(event) {
4736
5298
  if (!this.isRecord(event)) {
@@ -5258,6 +5820,46 @@ class PraxisCrudComponent {
5258
5820
  }
5259
5821
  return cfg.toolbar;
5260
5822
  };
5823
+ if (meta?.filterBar) {
5824
+ const fb = meta.filterBar;
5825
+ const toolbar = ensureToolbar();
5826
+ toolbar.visible = true;
5827
+ toolbar.filters = toolbar.filters || { enabled: true, showAdvancedButton: true };
5828
+ toolbar.filters.enabled = true;
5829
+ if (fb.showAdvanced !== undefined) {
5830
+ toolbar.filters.showAdvancedButton = fb.showAdvanced;
5831
+ }
5832
+ if (Array.isArray(fb.quickFilters)) {
5833
+ toolbar.filters.quickFilters = [...fb.quickFilters];
5834
+ }
5835
+ else if (fb.quickFilters === false) {
5836
+ toolbar.filters.quickFilters = [];
5837
+ }
5838
+ if (Array.isArray(fb.inlineFields) && fb.inlineFields.length > 0) {
5839
+ const behavior = (cfg.behavior = cfg.behavior || {});
5840
+ const filtering = (behavior.filtering = behavior.filtering || {
5841
+ enabled: true,
5842
+ strategy: 'server',
5843
+ debounceTime: 300,
5844
+ });
5845
+ filtering.enabled = true;
5846
+ const advancedFilters = (filtering.advancedFilters = filtering.advancedFilters || { enabled: true });
5847
+ advancedFilters.enabled = true;
5848
+ const settings = (advancedFilters.settings = advancedFilters.settings || {});
5849
+ settings.alwaysVisibleFields = [...fb.inlineFields];
5850
+ if (fb.showAdvanced !== undefined) {
5851
+ settings.showAdvanced = fb.showAdvanced;
5852
+ }
5853
+ }
5854
+ changed = true;
5855
+ }
5856
+ else if (this.supportsFilterCapability(capabilities) && !cfg.toolbar?.filters && !cfg.behavior?.filtering?.advancedFilters) {
5857
+ const toolbar = ensureToolbar();
5858
+ toolbar.visible = true;
5859
+ toolbar.filters = toolbar.filters || { enabled: true, showAdvancedButton: true };
5860
+ toolbar.filters.enabled = true;
5861
+ changed = true;
5862
+ }
5261
5863
  const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.shouldCanonicalizeToolbarCreateAction(action));
5262
5864
  const addAction = this.resolveCreateToolbarAction(meta, capabilities);
5263
5865
  if (addAction) {
@@ -5384,6 +5986,20 @@ class PraxisCrudComponent {
5384
5986
  changed = true;
5385
5987
  }
5386
5988
  }
5989
+ const effectiveOpenMode = meta.defaults?.openMode;
5990
+ const hasDrawerAction = (meta.actions || []).some((action) => action.openMode === 'drawer');
5991
+ const drawerRequested = effectiveOpenMode === 'drawer' ||
5992
+ hasDrawerAction ||
5993
+ cfg.behavior?.drawer?.enabled === true;
5994
+ if (drawerRequested && !this.disableAutoDrawer) {
5995
+ cfg.behavior = cfg.behavior || {};
5996
+ cfg.behavior.drawer = {
5997
+ enabled: true,
5998
+ openOnRowClick: cfg.behavior.drawer?.openOnRowClick !== false,
5999
+ ...cfg.behavior.drawer,
6000
+ };
6001
+ changed = true;
6002
+ }
5387
6003
  if (!changed) {
5388
6004
  return base ? undefined : cfg;
5389
6005
  }
@@ -5500,7 +6116,8 @@ class PraxisCrudComponent {
5500
6116
  if (!requiredCapability) {
5501
6117
  return true;
5502
6118
  }
5503
- const capabilities = this.enterpriseRuntimeContext?.snapshot?.capabilities;
6119
+ const capabilities = this.enterpriseRuntimeContext?.effectiveContext?.capabilities ??
6120
+ this.enterpriseRuntimeContext?.snapshot?.capabilities;
5504
6121
  return Array.isArray(capabilities)
5505
6122
  && capabilities.some((capability) => capability === requiredCapability);
5506
6123
  }
@@ -5675,7 +6292,7 @@ class PraxisCrudComponent {
5675
6292
  return operationAvailability;
5676
6293
  }
5677
6294
  return (this.hasCanonicalOperation(snapshot, 'create') ||
5678
- snapshot.surfaces.some((surface) => surface.scope === 'COLLECTION' &&
6295
+ (snapshot.surfaces || []).some((surface) => surface.scope === 'COLLECTION' &&
5679
6296
  this.isWritableCrudSurface(surface) &&
5680
6297
  surface.availability?.allowed !== false));
5681
6298
  }
@@ -5691,6 +6308,12 @@ class PraxisCrudComponent {
5691
6308
  const operationAvailability = this.resolveOperationAvailability(snapshot, 'delete');
5692
6309
  return operationAvailability ?? this.hasCanonicalOperation(snapshot, 'delete');
5693
6310
  }
6311
+ supportsFilterCapability(snapshot) {
6312
+ if (!snapshot)
6313
+ return false;
6314
+ const operationAvailability = this.resolveOperationAvailability(snapshot, 'filter');
6315
+ return operationAvailability ?? this.hasCanonicalOperation(snapshot, 'filter');
6316
+ }
5694
6317
  resolveOperationAvailability(snapshot, operationId) {
5695
6318
  const operation = snapshot.operations?.[operationId];
5696
6319
  if (!operation) {
@@ -5838,7 +6461,7 @@ class PraxisCrudComponent {
5838
6461
  return Object.entries(params).reduce((current, [name, value]) => current.split(`{${name}}`).join(value), text);
5839
6462
  }
5840
6463
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5841
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisCrudComponent, isStandalone: true, selector: "praxis-crud", inputs: { metadata: "metadata", crudId: "crudId", componentInstanceId: "componentInstanceId", tableConfigPersistenceStrategy: "tableConfigPersistenceStrategy", context: "context", enableCustomization: "enableCustomization", showResourceIdentityKey: "showResourceIdentityKey", authoringCapability: "authoringCapability" }, 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", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
6464
+ 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", kpiCardClick: "kpiCardClick" }, providers: [
5842
6465
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
5843
6466
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
5844
6467
  ], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
@@ -5849,6 +6472,14 @@ class PraxisCrudComponent {
5849
6472
  <span>{{ getOpeningActionStatus(actionLabel) }}</span>
5850
6473
  </div>
5851
6474
  }
6475
+ @if (resolvedKpiBandConfig) {
6476
+ <praxis-kpi-band
6477
+ [config]="resolvedKpiBandConfig"
6478
+ [dataContext]="kpiDataContext"
6479
+ [isInteractiveHost]="isKpiInteractiveHost"
6480
+ (cardClick)="onKpiCardClick($event)"
6481
+ />
6482
+ }
5852
6483
  @if (shouldRenderTable(resolvedMetadata)) {
5853
6484
  <praxis-table
5854
6485
  [config]="tableConfigForBinding"
@@ -5856,6 +6487,7 @@ class PraxisCrudComponent {
5856
6487
  [data]="resolveTableData(resolvedMetadata)"
5857
6488
  [queryContext]="tableQueryContext"
5858
6489
  [filterCriteria]="tableFilterCriteria"
6490
+ [filterTeleport]="filterTeleport"
5859
6491
  [tableId]="crudId || 'default'"
5860
6492
  [configPersistenceStrategy]="resolvedTableConfigPersistenceStrategy"
5861
6493
  [crudContext]="tableCrudContext"
@@ -5872,6 +6504,8 @@ class PraxisCrudComponent {
5872
6504
  (configChange)="onTableConfigChange($event)"
5873
6505
  (metadataChange)="onTableMetadataChange()"
5874
6506
  (loadingStateChange)="onTableLoadingStateChange($event)"
6507
+ (drawerOpen)="onTableDrawerOpen($event)"
6508
+ (drawerClose)="onTableDrawerClose()"
5875
6509
  ></praxis-table>
5876
6510
  } @else {
5877
6511
  @if (isCustomizationAvailable()) {
@@ -5884,11 +6518,11 @@ class PraxisCrudComponent {
5884
6518
  }
5885
6519
  }
5886
6520
  </div>
5887
- `, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}.praxis-crud-runtime{position:relative;min-width:0}.praxis-crud-opening-status{position:absolute;z-index:6;top:.7rem;right:.7rem;display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;max-width:min(24rem,calc(100% - 1.4rem));padding:.48rem .72rem;border:1px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 32%,transparent);border-radius:999px;color:var(--md-sys-color-on-surface, currentColor);background:color-mix(in srgb,var(--md-sys-color-surface-container-high, #fff) 94%,transparent);box-shadow:0 10px 28px #0f172a29;font-size:.82rem;font-weight:650;line-height:1.25;pointer-events:none}.praxis-crud-opening-status__spinner{width:.95rem;height:.95rem;flex:0 0 auto;border:2px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 26%,transparent);border-top-color:var(--md-sys-color-primary, #3f51b5);border-radius:50%;animation:praxis-crud-opening-spin .72s linear infinite}@keyframes praxis-crud-opening-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.praxis-crud-opening-status__spinner{animation:none;border-top-color:currentColor}}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "configPersistenceStrategy", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "queryContext", "aiContext", "aiAssistantVoiceInputMode", "aiAssistantVoiceLanguage", "horizontalScroll", "enableCustomization", "authoringCapability", "dense"], outputs: ["rowClick", "widgetEvent", "resourceEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "columnResize", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone", "variant", "alignment", "density", "iconContainer"] }] });
6521
+ `, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}.praxis-crud-runtime{position:relative;min-width:0}.praxis-crud-opening-status{position:absolute;z-index:6;top:.7rem;right:.7rem;display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;max-width:min(24rem,calc(100% - 1.4rem));padding:.48rem .72rem;border:1px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 32%,transparent);border-radius:999px;color:var(--md-sys-color-on-surface, currentColor);background:color-mix(in srgb,var(--md-sys-color-surface-container-high, #fff) 94%,transparent);box-shadow:0 10px 28px #0f172a29;font-size:.82rem;font-weight:650;line-height:1.25;pointer-events:none}.praxis-crud-opening-status__spinner{width:.95rem;height:.95rem;flex:0 0 auto;border:2px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 26%,transparent);border-top-color:var(--md-sys-color-primary, #3f51b5);border-radius:50%;animation:praxis-crud-opening-spin .72s linear infinite}@keyframes praxis-crud-opening-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.praxis-crud-opening-status__spinner{animation:none;border-top-color:currentColor}}\n"], dependencies: [{ kind: "component", type: PraxisTable, selector: "praxis-table", inputs: ["config", "resourcePath", "data", "tableId", "componentInstanceId", "configPersistenceStrategy", "title", "subtitle", "icon", "autoDelete", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "crudContext", "filterCriteria", "filterTeleport", "widgetId", "queryContext", "aiContext", "aiAssistantVoiceInputMode", "aiAssistantVoiceLanguage", "horizontalScroll", "enableCustomization", "authoringCapability", "dense"], outputs: ["queryContextClear", "queryContextChange", "rowClick", "widgetEvent", "resourceEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "columnResize", "beforeDelete", "afterDelete", "deleteError", "beforeBulkDelete", "afterBulkDelete", "bulkDeleteError", "schemaStatusChange", "configChange", "metadataChange", "loadingStateChange", "collectionLinksChange", "selectionChange", "drawerOpen", "drawerClose", "drawerTabChange"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone", "variant", "alignment", "density", "iconContainer"] }, { kind: "component", type: PraxisKpiBandComponent, selector: "praxis-kpi-band", inputs: ["config", "dataContext", "isInteractiveHost"], outputs: ["cardClick", "statsLoaded", "statsError", "refreshClick"] }] });
5888
6522
  }
5889
6523
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
5890
6524
  type: Component,
5891
- args: [{ selector: 'praxis-crud', standalone: true, imports: [PraxisTable, EmptyStateCardComponent], providers: [
6525
+ args: [{ selector: 'praxis-crud', standalone: true, imports: [PraxisTable, EmptyStateCardComponent, PraxisKpiBandComponent], providers: [
5892
6526
  providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
5893
6527
  providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
5894
6528
  ], template: `
@@ -5899,6 +6533,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5899
6533
  <span>{{ getOpeningActionStatus(actionLabel) }}</span>
5900
6534
  </div>
5901
6535
  }
6536
+ @if (resolvedKpiBandConfig) {
6537
+ <praxis-kpi-band
6538
+ [config]="resolvedKpiBandConfig"
6539
+ [dataContext]="kpiDataContext"
6540
+ [isInteractiveHost]="isKpiInteractiveHost"
6541
+ (cardClick)="onKpiCardClick($event)"
6542
+ />
6543
+ }
5902
6544
  @if (shouldRenderTable(resolvedMetadata)) {
5903
6545
  <praxis-table
5904
6546
  [config]="tableConfigForBinding"
@@ -5906,6 +6548,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5906
6548
  [data]="resolveTableData(resolvedMetadata)"
5907
6549
  [queryContext]="tableQueryContext"
5908
6550
  [filterCriteria]="tableFilterCriteria"
6551
+ [filterTeleport]="filterTeleport"
5909
6552
  [tableId]="crudId || 'default'"
5910
6553
  [configPersistenceStrategy]="resolvedTableConfigPersistenceStrategy"
5911
6554
  [crudContext]="tableCrudContext"
@@ -5922,6 +6565,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5922
6565
  (configChange)="onTableConfigChange($event)"
5923
6566
  (metadataChange)="onTableMetadataChange()"
5924
6567
  (loadingStateChange)="onTableLoadingStateChange($event)"
6568
+ (drawerOpen)="onTableDrawerOpen($event)"
6569
+ (drawerClose)="onTableDrawerClose()"
5925
6570
  ></praxis-table>
5926
6571
  } @else {
5927
6572
  @if (isCustomizationAvailable()) {
@@ -5936,11 +6581,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5936
6581
  </div>
5937
6582
  `, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}.praxis-crud-runtime{position:relative;min-width:0}.praxis-crud-opening-status{position:absolute;z-index:6;top:.7rem;right:.7rem;display:inline-flex;align-items:center;gap:.55rem;min-height:2.25rem;max-width:min(24rem,calc(100% - 1.4rem));padding:.48rem .72rem;border:1px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 32%,transparent);border-radius:999px;color:var(--md-sys-color-on-surface, currentColor);background:color-mix(in srgb,var(--md-sys-color-surface-container-high, #fff) 94%,transparent);box-shadow:0 10px 28px #0f172a29;font-size:.82rem;font-weight:650;line-height:1.25;pointer-events:none}.praxis-crud-opening-status__spinner{width:.95rem;height:.95rem;flex:0 0 auto;border:2px solid color-mix(in srgb,var(--md-sys-color-primary, #3f51b5) 26%,transparent);border-top-color:var(--md-sys-color-primary, #3f51b5);border-radius:50%;animation:praxis-crud-opening-spin .72s linear infinite}@keyframes praxis-crud-opening-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.praxis-crud-opening-status__spinner{animation:none;border-top-color:currentColor}}\n"] }]
5938
6583
  }], ctorParameters: () => [], propDecorators: { metadata: [{
5939
- type: Input,
5940
- args: [{ required: true }]
6584
+ type: Input
5941
6585
  }], crudId: [{
5942
- type: Input,
5943
- args: [{ required: true }]
6586
+ type: Input
6587
+ }], resource: [{
6588
+ type: Input
5944
6589
  }], componentInstanceId: [{
5945
6590
  type: Input
5946
6591
  }], tableConfigPersistenceStrategy: [{
@@ -5953,6 +6598,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5953
6598
  type: Input
5954
6599
  }], authoringCapability: [{
5955
6600
  type: Input
6601
+ }], filterCriteria: [{
6602
+ type: Input
6603
+ }], filterTeleport: [{
6604
+ type: Input,
6605
+ args: [{ transform: booleanAttribute }]
6606
+ }], disableAutoDrawer: [{
6607
+ type: Input,
6608
+ args: [{ transform: booleanAttribute }]
5956
6609
  }], configureRequested: [{
5957
6610
  type: Output
5958
6611
  }], afterOpen: [{
@@ -5979,10 +6632,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
5979
6632
  type: Output
5980
6633
  }], resourceEvent: [{ type: i0.Output, args: ["resourceEvent"] }], tableRuntimeConfigChange: [{
5981
6634
  type: Output
6635
+ }], drawerOpen: [{
6636
+ type: Output
6637
+ }], drawerClose: [{
6638
+ type: Output
5982
6639
  }], crudAuthoringDocumentApplied: [{
5983
6640
  type: Output
5984
6641
  }], crudAuthoringDocumentSaved: [{
5985
6642
  type: Output
6643
+ }], kpiCardClick: [{
6644
+ type: Output
5986
6645
  }], table: [{
5987
6646
  type: ViewChild,
5988
6647
  args: [PraxisTable]
@@ -6703,7 +7362,7 @@ class DynamicFormDialogHostComponent {
6703
7362
  (reactiveDeterminationPendingChange)="onReactiveDeterminationPendingChange($event)"
6704
7363
  ></praxis-dynamic-form>
6705
7364
  </mat-dialog-content>
6706
- `, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-operation-context{position:sticky;top:0;z-index:1;display:grid;gap:.75rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity{display:grid;gap:.25rem;min-width:0}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{display:flex;flex-direction:column;max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content>praxis-dynamic-form{display:block;flex:1 1 auto;min-height:0}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content>praxis-dynamic-form>.praxis-dynamic-form{min-height:100%}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content>praxis-dynamic-form>.praxis-dynamic-form>praxis-form-actions[data-actions-placement=afterSections]{margin-top:auto;padding-top:var(--pfx-actions-gap-top, var(--pfx-section-gap, 20px))}praxis-dynamic-form-dialog-host.praxis-surface-frame .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKey", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "emptyState", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "submitIdempotencyKey", "submitCorrelationId", "submitResourceVersion", "submitResourceVersionTargetResourceKey", "submitResourceVersionTargetIdField", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "presentation", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "payloadPreviewChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange", "reactiveDeterminationExecuted", "reactiveDeterminationPendingChange"] }], encapsulation: i0.ViewEncapsulation.None });
7365
+ `, isInline: true, styles: ["praxis-dynamic-form-dialog-host{--dlg-header-h: 56px;--dlg-footer-h: 56px;--dlg-pad: 16px;display:flex;flex-direction:column;height:100%;overflow:hidden}praxis-dynamic-form-dialog-host[data-density=compact]{--dlg-header-h: 44px;--dlg-footer-h: 44px;--dlg-pad: 12px}praxis-dynamic-form-dialog-host .dialog-header{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--dlg-pad);min-height:var(--dlg-header-h);padding:10px var(--dlg-pad);margin:0;background:var(--md-sys-color-surface-container-high);border-bottom:1px solid var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-title{margin:0;font:inherit;font-weight:600;color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host .dialog-heading{display:grid;min-width:0;gap:2px}praxis-dynamic-form-dialog-host .dialog-title-icon{display:inline-grid;flex:0 0 auto;width:36px;height:36px;place-items:center;border-radius:10px;color:var(--md-sys-color-primary);background:color-mix(in srgb,var(--md-sys-color-primary) 12%,transparent)}praxis-dynamic-form-dialog-host .dialog-title-icon mat-icon{width:22px;height:22px;font-size:22px}praxis-dynamic-form-dialog-host .dialog-subtitle{display:-webkit-box;overflow:hidden;color:var(--md-sys-color-on-surface-variant);font-size:.82rem;font-weight:400;line-height:1.35;-webkit-box-orient:vertical;-webkit-line-clamp:2}praxis-dynamic-form-dialog-host .spacer{flex:1}praxis-dynamic-form-dialog-host .dialog-content{flex:1 1 auto;overflow:auto;padding:var(--dlg-pad);max-height:calc(100svh - var(--dlg-header-h) - 32px)}praxis-dynamic-form-dialog-host .crud-operation-context{position:sticky;top:0;z-index:1;display:grid;gap:.75rem;margin-block-end:var(--dlg-pad);padding-block:.5rem .75rem;background:var(--pfx-form-surface, var(--md-sys-color-surface));border-bottom:1px solid var(--md-sys-color-outline-variant)}praxis-dynamic-form-dialog-host .crud-resource-identity{display:grid;gap:.25rem;min-width:0}praxis-dynamic-form-dialog-host .crud-resource-identity__label{color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-label-medium, 500 .75rem/1rem inherit)}praxis-dynamic-form-dialog-host.praxis-drawer{--dlg-header-h: 72px;width:100%;min-width:0;max-width:100vw;height:100dvh;background:var(--pfx-form-surface, var(--md-sys-color-surface));color:var(--md-sys-color-on-surface)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-header{background:color-mix(in srgb,var(--md-sys-color-surface-container-high, var(--md-sys-color-surface)),transparent 4%)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content{display:flex;flex-direction:column;max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content>praxis-dynamic-form{display:block;flex:1 1 auto;min-height:0}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content>praxis-dynamic-form>.praxis-dynamic-form{min-height:100%}praxis-dynamic-form-dialog-host.praxis-drawer .dialog-content>praxis-dynamic-form>.praxis-dynamic-form>praxis-form-actions[data-actions-placement=afterSections]{margin-top:auto;padding-top:var(--pfx-actions-gap-top, var(--pfx-section-gap, 20px))}praxis-dynamic-form-dialog-host.praxis-surface-frame .dialog-content{max-height:none;min-height:0;padding:clamp(12px,2.4vw,24px)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.praxis-icon-button:hover{color:var(--md-sys-color-primary)}praxis-dynamic-form-dialog-host .dialog-footer{position:sticky;bottom:0;z-index:1;padding:var(--dlg-pad)}.pfx-blur-backdrop{background-color:var(--pfx-backdrop, rgba(15, 23, 42, .42))!important;backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%);-webkit-backdrop-filter:blur(var(--pfx-backdrop-blur, 10px)) saturate(110%)}.pfx-transparent-backdrop{background-color:transparent!important}.cdk-overlay-pane.pfx-dialog-pane{overflow:hidden;transition:width .2s ease,height .2s ease,margin .2s ease}.cdk-overlay-pane.pfx-dialog-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane .mdc-dialog__surface{display:flex;flex-direction:column;width:100%;min-width:0;height:100%;max-height:inherit;overflow:hidden;background:var(--pfx-form-surface, var(--md-sys-color-surface))!important;border:1px solid var(--pfx-form-stroke, var(--md-sys-color-outline-variant))!important}.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-dialog-pane.pfx-dialog-frosted .mdc-dialog__surface{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.cdk-overlay-pane.pfx-drawer-pane{margin:0!important;border-radius:0!important;box-shadow:var(--md-sys-elevation-level3, 0 24px 80px rgba(15, 23, 42, .28)),0 0 0 1px color-mix(in srgb,var(--md-sys-color-outline, #64748b) 24%,transparent)}.cdk-overlay-pane.pfx-drawer-pane.pfx-drawer-maximized{margin:var(--pfx-drawer-edge-gap, 8px)!important}.cdk-overlay-pane.pfx-drawer-pane .mat-mdc-dialog-surface,.cdk-overlay-pane.pfx-drawer-pane .mdc-dialog__surface{min-width:0!important;border-radius:0!important}\n"], dependencies: [{ kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1$1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1$1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKey", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "emptyState", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "submitIdempotencyKey", "submitCorrelationId", "submitResourceVersion", "submitResourceVersionTargetResourceKey", "submitResourceVersionTargetIdField", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "layoutMode", "formLayout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "presentation", "presentationPreset", "sectionDefaultAppearance", "sectionDivider", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "payloadPreviewChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange", "reactiveDeterminationExecuted", "reactiveDeterminationPendingChange", "tabChange", "tabSelected"] }], encapsulation: i0.ViewEncapsulation.None });
6707
7366
  }
6708
7367
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
6709
7368
  type: Component,
@@ -6952,8 +7611,8 @@ function lowerFirstLetter(value) {
6952
7611
  }
6953
7612
 
6954
7613
  var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
6955
- __proto__: null,
6956
- DynamicFormDialogHostComponent: DynamicFormDialogHostComponent
7614
+ __proto__: null,
7615
+ DynamicFormDialogHostComponent: DynamicFormDialogHostComponent
6957
7616
  });
6958
7617
 
6959
7618
  class PraxisCrudWidgetConfigEditor {
@@ -7638,7 +8297,7 @@ class CrudPageHeaderComponent {
7638
8297
  <ng-content></ng-content>
7639
8298
  </div>
7640
8299
  </header>
7641
- `, isInline: true, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface)}.crud-header.sticky{position:sticky;top:0;z-index:10;-webkit-backdrop-filter:saturate(110%);backdrop-filter:saturate(110%)}.crud-header.with-divider{border-bottom:1px solid var(--md-sys-color-outline-variant)}.left{display:flex;align-items:center;gap:8px;min-width:0}.right{display:flex;align-items:center;gap:8px}.title{margin:0;font-weight:600;color:var(--md-sys-color-on-surface, currentColor);font:var(--mdc-typography-title-large, 600 20px/28px system-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.back-btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.back-btn .mat-mdc-button{padding-left:0}.back-btn mat-icon{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn .label{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn.ghost{border-radius:20px;padding:4px 8px}.back-btn.ghost:hover{background:var(--md-sys-color-primary-container)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container);box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant)}.back-btn.tonal:hover{background:var(--md-sys-color-primary-container)}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.back-btn.outlined:hover{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}@media(max-width:599px){.label.hide-on-narrow{display:none}}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.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: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
8300
+ `, isInline: true, styles: [".crud-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 0;background:var(--md-sys-color-surface)}.crud-header.sticky{position:sticky;top:0;z-index:10;-webkit-backdrop-filter:saturate(110%);backdrop-filter:saturate(110%)}.crud-header.with-divider{border-bottom:1px solid var(--md-sys-color-outline-variant)}.left{display:flex;align-items:center;gap:8px;min-width:0}.right{display:flex;align-items:center;gap:8px}.title{margin:0;font-weight:600;color:var(--md-sys-color-on-surface, currentColor);font:var(--mdc-typography-title-large, 600 20px/28px system-ui);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.back-btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none}.back-btn .mat-mdc-button{padding-left:0}.back-btn mat-icon{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn .label{color:var(--md-sys-color-on-surface-variant, currentColor)}.back-btn.ghost{border-radius:20px;padding:4px 8px}.back-btn.ghost:hover{background:var(--md-sys-color-primary-container)}.back-btn.tonal{border-radius:20px;padding:4px 10px;background:var(--md-sys-color-surface-container);box-shadow:inset 0 0 0 1px var(--md-sys-color-outline-variant)}.back-btn.tonal:hover{background:var(--md-sys-color-primary-container)}.back-btn.outlined{border-radius:20px;padding:4px 10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.back-btn.outlined:hover{border-color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}@media(max-width:599px){.label.hide-on-narrow{display:none}}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { 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: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }] });
7642
8301
  }
7643
8302
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: CrudPageHeaderComponent, decorators: [{
7644
8303
  type: Component,
@@ -8241,6 +8900,522 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
8241
8900
  ],
8242
8901
  };
8243
8902
 
8903
+ /**
8904
+ * Praxis UI Platform — Zero-Code Canonical Resource Page (Issue #38, #40)
8905
+ *
8906
+ * Componente governado de página inteira que orquestra automaticamente a experiência
8907
+ * completa de um recurso corporativo a partir de sua coordenada RESTful ('resource' / 'resourcePath').
8908
+ *
8909
+ * Suporta nativamente:
8910
+ * - Auto-Header Governado (domínio, título, subtítulo e ações)
8911
+ * - Auto-ScopeBar Integrado (filtros rápidos em chips conectados diretamente ao CRUD)
8912
+ * - Auto-KPI Band Nativo (sincronização bidirecional entre cards e escopos)
8913
+ * - Customização de colunas e tabela via configuração declarativa
8914
+ * - Isolamento multi-tenant / persona com reload seguro em trocas de identidade
8915
+ *
8916
+ * Pode ser utilizado diretamente como alvo de rotas do Angular:
8917
+ * ```typescript
8918
+ * {
8919
+ * path: 'missoes',
8920
+ * component: PraxisResourcePage,
8921
+ * data: {
8922
+ * resource: 'operations/missoes',
8923
+ * title: 'Centro de Missões',
8924
+ * subtitle: 'Planejamento e acompanhamento em campo.',
8925
+ * domain: { label: 'Operações Táticas', icon: 'military_tech', tone: 'operations' },
8926
+ * scopeBar: { ... }
8927
+ * }
8928
+ * }
8929
+ * ```
8930
+ */
8931
+ class PraxisResourcePage {
8932
+ route = inject(ActivatedRoute, { optional: true });
8933
+ routeSub;
8934
+ resource;
8935
+ resourcePath;
8936
+ crudId;
8937
+ metadata;
8938
+ title;
8939
+ subtitle;
8940
+ domain;
8941
+ scopeBar;
8942
+ scopeItems;
8943
+ defaultScopeId;
8944
+ enableCustomization = false;
8945
+ tableConfigPersistenceStrategy = 'local-first';
8946
+ context;
8947
+ actions;
8948
+ filterCriteria;
8949
+ rowClick = new EventEmitter();
8950
+ rowAction = new EventEmitter();
8951
+ rowDoubleClick = new EventEmitter();
8952
+ selectionChange = new EventEmitter();
8953
+ drawerOpen = new EventEmitter();
8954
+ drawerClose = new EventEmitter();
8955
+ afterOpen = new EventEmitter();
8956
+ afterClose = new EventEmitter();
8957
+ kpiCardClick = new EventEmitter();
8958
+ afterSave = new EventEmitter();
8959
+ afterDelete = new EventEmitter();
8960
+ error = new EventEmitter();
8961
+ routeResourceSignal = signal(null, ...(ngDevMode ? [{ debugName: "routeResourceSignal" }] : /* istanbul ignore next */ []));
8962
+ routeMetadataSignal = signal(null, ...(ngDevMode ? [{ debugName: "routeMetadataSignal" }] : /* istanbul ignore next */ []));
8963
+ routeTitleSignal = signal(null, ...(ngDevMode ? [{ debugName: "routeTitleSignal" }] : /* istanbul ignore next */ []));
8964
+ routeSubtitleSignal = signal(null, ...(ngDevMode ? [{ debugName: "routeSubtitleSignal" }] : /* istanbul ignore next */ []));
8965
+ routeDomainSignal = signal(null, ...(ngDevMode ? [{ debugName: "routeDomainSignal" }] : /* istanbul ignore next */ []));
8966
+ routeScopeBarSignal = signal(null, ...(ngDevMode ? [{ debugName: "routeScopeBarSignal" }] : /* istanbul ignore next */ []));
8967
+ routeCustomizationSignal = signal(null, ...(ngDevMode ? [{ debugName: "routeCustomizationSignal" }] : /* istanbul ignore next */ []));
8968
+ identityRenderKey = signal(0, ...(ngDevMode ? [{ debugName: "identityRenderKey" }] : /* istanbul ignore next */ []));
8969
+ identityVisible = signal(true, ...(ngDevMode ? [{ debugName: "identityVisible" }] : /* istanbul ignore next */ []));
8970
+ activeScopeId = signal('all', ...(ngDevMode ? [{ debugName: "activeScopeId" }] : /* istanbul ignore next */ []));
8971
+ activeScopeFilter = signal({}, ...(ngDevMode ? [{ debugName: "activeScopeFilter" }] : /* istanbul ignore next */ []));
8972
+ effectiveResource = computed(() => {
8973
+ return this.resource || this.resourcePath || this.routeResourceSignal();
8974
+ }, ...(ngDevMode ? [{ debugName: "effectiveResource" }] : /* istanbul ignore next */ []));
8975
+ effectiveTitle = computed(() => {
8976
+ return this.title || this.routeTitleSignal();
8977
+ }, ...(ngDevMode ? [{ debugName: "effectiveTitle" }] : /* istanbul ignore next */ []));
8978
+ effectiveSubtitle = computed(() => {
8979
+ return this.subtitle || this.routeSubtitleSignal();
8980
+ }, ...(ngDevMode ? [{ debugName: "effectiveSubtitle" }] : /* istanbul ignore next */ []));
8981
+ effectiveDomain = computed(() => {
8982
+ return this.domain || this.routeDomainSignal();
8983
+ }, ...(ngDevMode ? [{ debugName: "effectiveDomain" }] : /* istanbul ignore next */ []));
8984
+ effectiveEnableCustomization = computed(() => {
8985
+ return this.enableCustomization || this.routeCustomizationSignal() || false;
8986
+ }, ...(ngDevMode ? [{ debugName: "effectiveEnableCustomization" }] : /* istanbul ignore next */ []));
8987
+ effectiveScopeBar = computed(() => {
8988
+ if (this.scopeBar)
8989
+ return this.scopeBar;
8990
+ if (this.scopeItems && this.scopeItems.length > 0) {
8991
+ return {
8992
+ items: this.scopeItems,
8993
+ leadLabel: 'Filtro:',
8994
+ leadIcon: 'tune',
8995
+ };
8996
+ }
8997
+ const fromRoute = this.routeScopeBarSignal();
8998
+ if (fromRoute)
8999
+ return fromRoute;
9000
+ // Auto-promoção declarativa a partir de metadata.table.toolbar.filters.quickFilters
9001
+ const metaSource = this.metadata || this.routeMetadataSignal();
9002
+ let meta = null;
9003
+ if (typeof metaSource === 'string' && metaSource.trim().startsWith('{')) {
9004
+ try {
9005
+ meta = JSON.parse(metaSource);
9006
+ }
9007
+ catch { }
9008
+ }
9009
+ else if (metaSource && typeof metaSource === 'object') {
9010
+ meta = metaSource;
9011
+ }
9012
+ const quickFilters = meta?.table?.toolbar?.filters?.quickFilters;
9013
+ if (Array.isArray(quickFilters) && quickFilters.length > 0) {
9014
+ return {
9015
+ items: quickFilters.map((qf) => ({
9016
+ id: qf.id,
9017
+ label: qf.label,
9018
+ filter: qf.filter,
9019
+ icon: qf.icon,
9020
+ tone: qf.tone,
9021
+ badge: qf.badge,
9022
+ count: qf.count,
9023
+ })),
9024
+ leadLabel: 'Filtro:',
9025
+ leadIcon: 'tune',
9026
+ };
9027
+ }
9028
+ return null;
9029
+ }, ...(ngDevMode ? [{ debugName: "effectiveScopeBar" }] : /* istanbul ignore next */ []));
9030
+ resolvedCrudId = computed(() => {
9031
+ if (this.crudId)
9032
+ return this.crudId;
9033
+ const res = this.effectiveResource();
9034
+ return res ? `crud-${res.replace(/\//g, '-')}` : 'praxis-crud-auto';
9035
+ }, ...(ngDevMode ? [{ debugName: "resolvedCrudId" }] : /* istanbul ignore next */ []));
9036
+ effectiveMetadata = computed(() => {
9037
+ const metaSource = this.metadata || this.routeMetadataSignal();
9038
+ let base;
9039
+ if (typeof metaSource === 'string') {
9040
+ const trimmed = metaSource.trim();
9041
+ base = trimmed.startsWith('{')
9042
+ ? JSON.parse(trimmed)
9043
+ : {
9044
+ component: 'praxis-crud',
9045
+ resource: { path: trimmed, idField: 'id' },
9046
+ table: { columns: [] },
9047
+ };
9048
+ }
9049
+ else if (metaSource) {
9050
+ base = { ...metaSource };
9051
+ }
9052
+ else {
9053
+ const res = this.effectiveResource() || 'recurso-auto';
9054
+ base = {
9055
+ component: 'praxis-crud',
9056
+ resource: { path: res, idField: 'id' },
9057
+ table: { columns: [] },
9058
+ };
9059
+ }
9060
+ if (this.actions) {
9061
+ base.actions = [...(base.actions || []), ...this.actions];
9062
+ }
9063
+ // Se o ScopeBar estiver ativo no cabeçalho (seja manual ou auto-promovido),
9064
+ // suprimir os botões redundantes de quickFilters na toolbar interna da tabela
9065
+ if (this.effectiveScopeBar() && base.table?.toolbar?.filters) {
9066
+ base.table = {
9067
+ ...base.table,
9068
+ toolbar: {
9069
+ ...base.table.toolbar,
9070
+ filters: {
9071
+ ...base.table.toolbar.filters,
9072
+ showQuickFilters: false,
9073
+ },
9074
+ },
9075
+ };
9076
+ }
9077
+ // Mesclar filterCriteria com filtro tático do ScopeBar ativo
9078
+ const combinedFilterCriteria = {
9079
+ ...(base.filterCriteria || {}),
9080
+ ...(this.filterCriteria || {}),
9081
+ ...this.activeScopeFilter(),
9082
+ };
9083
+ base.filterCriteria = combinedFilterCriteria;
9084
+ return base;
9085
+ }, ...(ngDevMode ? [{ debugName: "effectiveMetadata" }] : /* istanbul ignore next */ []));
9086
+ defaultMetadata = computed(() => {
9087
+ return this.effectiveMetadata();
9088
+ }, ...(ngDevMode ? [{ debugName: "defaultMetadata" }] : /* istanbul ignore next */ []));
9089
+ onIdentitySwitch = () => {
9090
+ this.activeScopeFilter.set({});
9091
+ this.activeScopeId.set(this.defaultScopeId || 'all');
9092
+ this.identityRenderKey.update((k) => k + 1);
9093
+ this.identityVisible.set(false);
9094
+ queueMicrotask(() => this.identityVisible.set(true));
9095
+ };
9096
+ ngOnInit() {
9097
+ const sb = this.effectiveScopeBar();
9098
+ const initialId = this.defaultScopeId ||
9099
+ sb?.activeId ||
9100
+ sb?.items?.find((i) => i.isDefault)?.id ||
9101
+ 'all';
9102
+ this.activeScopeId.set(initialId);
9103
+ const initialItem = sb?.items?.find((i) => i.id === initialId);
9104
+ if (initialItem) {
9105
+ this.applyItemFilter(initialItem);
9106
+ }
9107
+ if (typeof window !== 'undefined') {
9108
+ window.addEventListener('praxis:identity-switch', this.onIdentitySwitch);
9109
+ }
9110
+ if (this.route) {
9111
+ // 1. Extração estática síncrona
9112
+ const data = this.route.snapshot.data || {};
9113
+ const params = this.route.snapshot.params || {};
9114
+ const dataResource = data['resource'] || data['resourcePath'];
9115
+ const paramResource = this.route.snapshot.paramMap?.get('resource') || params['resource'];
9116
+ const initialRes = dataResource || paramResource || null;
9117
+ if (initialRes) {
9118
+ this.routeResourceSignal.set(initialRes);
9119
+ }
9120
+ this.extractRouteData(data);
9121
+ // 2. Assinatura reativa para mudanças dinâmicas de rota
9122
+ this.routeSub = new Subscription();
9123
+ if (this.route.params) {
9124
+ this.routeSub.add(this.route.params.subscribe((p) => {
9125
+ if (p['resource']) {
9126
+ this.routeResourceSignal.set(p['resource']);
9127
+ }
9128
+ }));
9129
+ }
9130
+ if (this.route.data) {
9131
+ this.routeSub.add(this.route.data.subscribe((d) => {
9132
+ const nextRes = d['resource'] || d['resourcePath'];
9133
+ if (nextRes) {
9134
+ this.routeResourceSignal.set(nextRes);
9135
+ }
9136
+ this.extractRouteData(d);
9137
+ }));
9138
+ }
9139
+ }
9140
+ }
9141
+ ngOnDestroy() {
9142
+ this.routeSub?.unsubscribe();
9143
+ if (typeof window !== 'undefined') {
9144
+ window.removeEventListener('praxis:identity-switch', this.onIdentitySwitch);
9145
+ }
9146
+ }
9147
+ onScopeChange(item) {
9148
+ this.activeScopeId.set(item.id);
9149
+ this.applyItemFilter(item);
9150
+ }
9151
+ onScopeClear() {
9152
+ const sb = this.effectiveScopeBar();
9153
+ const defaultItem = sb?.items?.find((item) => item.isDefault) ||
9154
+ sb?.items?.find((item) => item.id === 'all');
9155
+ if (defaultItem) {
9156
+ this.onScopeChange(defaultItem);
9157
+ }
9158
+ else {
9159
+ this.activeScopeId.set('all');
9160
+ this.activeScopeFilter.set({});
9161
+ }
9162
+ }
9163
+ handleKpiCardClick(event) {
9164
+ const cardFilter = event.card.filter;
9165
+ if (cardFilter && typeof cardFilter === 'object') {
9166
+ const sb = this.effectiveScopeBar();
9167
+ if (sb && sb.items) {
9168
+ const matched = sb.items.find((item) => JSON.stringify(item.filter) === JSON.stringify(cardFilter));
9169
+ if (matched) {
9170
+ this.onScopeChange(matched);
9171
+ }
9172
+ }
9173
+ }
9174
+ this.kpiCardClick.emit(event);
9175
+ }
9176
+ applyItemFilter(item) {
9177
+ let filterObj = {};
9178
+ if (item.filter && typeof item.filter === 'object') {
9179
+ filterObj = item.filter;
9180
+ }
9181
+ else if (typeof item.filter === 'string') {
9182
+ const trimmed = item.filter.trim();
9183
+ if (trimmed.startsWith('{')) {
9184
+ try {
9185
+ filterObj = JSON.parse(trimmed);
9186
+ }
9187
+ catch {
9188
+ filterObj = { query: trimmed };
9189
+ }
9190
+ }
9191
+ else if (trimmed) {
9192
+ filterObj = { query: trimmed };
9193
+ }
9194
+ }
9195
+ this.activeScopeFilter.set(filterObj);
9196
+ }
9197
+ extractRouteData(data) {
9198
+ if ('metadata' in data) {
9199
+ this.routeMetadataSignal.set(data['metadata'] ?? null);
9200
+ }
9201
+ if ('title' in data) {
9202
+ this.routeTitleSignal.set(data['title'] ?? null);
9203
+ }
9204
+ if ('subtitle' in data) {
9205
+ this.routeSubtitleSignal.set(data['subtitle'] ?? null);
9206
+ }
9207
+ if ('domain' in data) {
9208
+ this.routeDomainSignal.set(data['domain'] ?? null);
9209
+ }
9210
+ if ('scopeBar' in data) {
9211
+ this.routeScopeBarSignal.set(data['scopeBar'] ?? null);
9212
+ }
9213
+ if (typeof data['enableCustomization'] === 'boolean') {
9214
+ this.routeCustomizationSignal.set(data['enableCustomization']);
9215
+ }
9216
+ }
9217
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisResourcePage, deps: [], target: i0.ɵɵFactoryTarget.Component });
9218
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisResourcePage, isStandalone: true, selector: "praxis-resource-page", inputs: { resource: "resource", resourcePath: "resourcePath", crudId: "crudId", metadata: "metadata", title: "title", subtitle: "subtitle", domain: "domain", scopeBar: "scopeBar", scopeItems: "scopeItems", defaultScopeId: "defaultScopeId", enableCustomization: "enableCustomization", tableConfigPersistenceStrategy: "tableConfigPersistenceStrategy", context: "context", actions: "actions", filterCriteria: "filterCriteria" }, outputs: { rowClick: "rowClick", rowAction: "rowAction", rowDoubleClick: "rowDoubleClick", selectionChange: "selectionChange", drawerOpen: "drawerOpen", drawerClose: "drawerClose", afterOpen: "afterOpen", afterClose: "afterClose", kpiCardClick: "kpiCardClick", afterSave: "afterSave", afterDelete: "afterDelete", error: "error" }, ngImport: i0, template: `
9219
+ <div class="praxis-resource-page page-container">
9220
+ <!-- Auto-Header Governado -->
9221
+ @if (effectiveTitle() || effectiveDomain()) {
9222
+ <header class="praxis-resource-page__header section-header">
9223
+ <div class="praxis-resource-page__header-main">
9224
+ @if (effectiveDomain(); as dom) {
9225
+ <div
9226
+ class="praxis-resource-page__domain-badge domain-tag"
9227
+ [ngClass]="dom.tone ? 'tone-' + dom.tone : ''"
9228
+ >
9229
+ @if (dom.icon) {
9230
+ <span class="material-symbols-outlined">{{ dom.icon }}</span>
9231
+ }
9232
+ <span>{{ dom.label }}</span>
9233
+ </div>
9234
+ }
9235
+ @if (effectiveTitle(); as t) {
9236
+ <h1 class="praxis-resource-page__title page-title title-gradient">{{ t }}</h1>
9237
+ }
9238
+ @if (effectiveSubtitle(); as sub) {
9239
+ <p class="praxis-resource-page__subtitle page-subtitle">{{ sub }}</p>
9240
+ }
9241
+ </div>
9242
+
9243
+ <div class="praxis-resource-page__header-actions">
9244
+ <ng-content select="[page-header-actions]"></ng-content>
9245
+ </div>
9246
+ </header>
9247
+ }
9248
+
9249
+ <!-- Auto-ScopeBar Integrado -->
9250
+ @if (effectiveScopeBar(); as sb) {
9251
+ <praxis-scope-bar
9252
+ [leadLabel]="sb.leadLabel || 'Filtro:'"
9253
+ [leadIcon]="sb.leadIcon || 'tune'"
9254
+ [items]="sb.items || []"
9255
+ [activeId]="activeScopeId()"
9256
+ [showClearButton]="sb.showClearButton ?? (activeScopeId() !== 'all')"
9257
+ [showOmnibox]="sb.showOmnibox ?? false"
9258
+ (scopeChange)="onScopeChange($event)"
9259
+ (clear)="onScopeClear()"
9260
+ />
9261
+ }
9262
+
9263
+ <!-- Praxis CRUD Runtime com isolamento reativo multi-tenant -->
9264
+ @if (effectiveResource() && identityVisible()) {
9265
+ <section class="praxis-resource-page__crud-surface crud-surface glass-panel">
9266
+ <praxis-crud
9267
+ [resource]="effectiveResource()!"
9268
+ [crudId]="resolvedCrudId()"
9269
+ [metadata]="effectiveMetadata()"
9270
+ [enableCustomization]="effectiveEnableCustomization()"
9271
+ [tableConfigPersistenceStrategy]="tableConfigPersistenceStrategy"
9272
+ [context]="context"
9273
+ (rowClick)="rowClick.emit($event)"
9274
+ (rowAction)="rowAction.emit($event)"
9275
+ (selectionChange)="selectionChange.emit($event)"
9276
+ (drawerOpen)="drawerOpen.emit($event)"
9277
+ (drawerClose)="drawerClose.emit()"
9278
+ (afterOpen)="afterOpen.emit($event)"
9279
+ (afterClose)="afterClose.emit()"
9280
+ (kpiCardClick)="handleKpiCardClick($event)"
9281
+ (afterSave)="afterSave.emit($event)"
9282
+ (afterDelete)="afterDelete.emit($event)"
9283
+ (error)="error.emit($event)"
9284
+ ></praxis-crud>
9285
+ </section>
9286
+ }
9287
+ </div>
9288
+ `, isInline: true, styles: [":host{display:block;width:100%;min-height:100%;box-sizing:border-box}.praxis-resource-page{width:100%;display:flex;flex-direction:column;gap:20px;padding:var(--praxis-page-padding, 24px);box-sizing:border-box;max-width:1540px;margin:0 auto}.praxis-resource-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.praxis-resource-page__header-main{min-width:0;display:flex;flex-direction:column}.praxis-resource-page__domain-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;align-self:flex-start;background:var(--praxis-domain-badge-bg, rgba(255, 255, 255, .08));color:var(--praxis-domain-badge-color, inherit);border:1px solid var(--praxis-domain-badge-border, rgba(255, 255, 255, .1))}.praxis-resource-page__domain-badge span.material-symbols-outlined{font-size:14px}.praxis-resource-page__title{margin:0 0 6px;font-size:var(--praxis-page-title-size, 2rem);font-weight:800;letter-spacing:-.02em;color:var(--praxis-page-title-color, var(--foreground, inherit));line-height:1.2}.praxis-resource-page__subtitle{margin:0;font-size:var(--praxis-page-subtitle-size, .9rem);color:var(--muted-foreground, #94a3b8);max-width:800px;line-height:1.5}.praxis-resource-page__crud-surface{width:100%;border-radius:var(--praxis-surface-radius, 16px);overflow:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: PraxisCrudComponent, selector: "praxis-crud", inputs: ["metadata", "crudId", "resource", "componentInstanceId", "tableConfigPersistenceStrategy", "context", "enableCustomization", "showResourceIdentityKey", "authoringCapability", "filterCriteria", "filterTeleport", "disableAutoDrawer"], outputs: ["configureRequested", "afterOpen", "afterClose", "afterSave", "afterDelete", "reactiveDeterminationExecuted", "reactiveDeterminationPendingChange", "reactiveDeterminationStabilityChange", "error", "rowClick", "rowAction", "selectionChange", "resourceEvent", "tableRuntimeConfigChange", "drawerOpen", "drawerClose", "crudAuthoringDocumentApplied", "crudAuthoringDocumentSaved", "kpiCardClick"] }, { kind: "component", type: PraxisScopeBarComponent, selector: "praxis-scope-bar", inputs: ["items", "activeId", "showOmnibox", "omniboxPlaceholder", "omniboxValue", "omniboxDebounceMs", "leadIcon", "leadLabel", "showClearButton", "clearLabel", "target", "targetWidgetId", "scopeBarId", "searchField"], outputs: ["activeIdChange", "omniboxValueChange", "scopeChange", "searchChange", "filterCriteriaChange", "clear"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9289
+ }
9290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisResourcePage, decorators: [{
9291
+ type: Component,
9292
+ args: [{ selector: 'praxis-resource-page', standalone: true, imports: [CommonModule, PraxisCrudComponent, PraxisScopeBarComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
9293
+ <div class="praxis-resource-page page-container">
9294
+ <!-- Auto-Header Governado -->
9295
+ @if (effectiveTitle() || effectiveDomain()) {
9296
+ <header class="praxis-resource-page__header section-header">
9297
+ <div class="praxis-resource-page__header-main">
9298
+ @if (effectiveDomain(); as dom) {
9299
+ <div
9300
+ class="praxis-resource-page__domain-badge domain-tag"
9301
+ [ngClass]="dom.tone ? 'tone-' + dom.tone : ''"
9302
+ >
9303
+ @if (dom.icon) {
9304
+ <span class="material-symbols-outlined">{{ dom.icon }}</span>
9305
+ }
9306
+ <span>{{ dom.label }}</span>
9307
+ </div>
9308
+ }
9309
+ @if (effectiveTitle(); as t) {
9310
+ <h1 class="praxis-resource-page__title page-title title-gradient">{{ t }}</h1>
9311
+ }
9312
+ @if (effectiveSubtitle(); as sub) {
9313
+ <p class="praxis-resource-page__subtitle page-subtitle">{{ sub }}</p>
9314
+ }
9315
+ </div>
9316
+
9317
+ <div class="praxis-resource-page__header-actions">
9318
+ <ng-content select="[page-header-actions]"></ng-content>
9319
+ </div>
9320
+ </header>
9321
+ }
9322
+
9323
+ <!-- Auto-ScopeBar Integrado -->
9324
+ @if (effectiveScopeBar(); as sb) {
9325
+ <praxis-scope-bar
9326
+ [leadLabel]="sb.leadLabel || 'Filtro:'"
9327
+ [leadIcon]="sb.leadIcon || 'tune'"
9328
+ [items]="sb.items || []"
9329
+ [activeId]="activeScopeId()"
9330
+ [showClearButton]="sb.showClearButton ?? (activeScopeId() !== 'all')"
9331
+ [showOmnibox]="sb.showOmnibox ?? false"
9332
+ (scopeChange)="onScopeChange($event)"
9333
+ (clear)="onScopeClear()"
9334
+ />
9335
+ }
9336
+
9337
+ <!-- Praxis CRUD Runtime com isolamento reativo multi-tenant -->
9338
+ @if (effectiveResource() && identityVisible()) {
9339
+ <section class="praxis-resource-page__crud-surface crud-surface glass-panel">
9340
+ <praxis-crud
9341
+ [resource]="effectiveResource()!"
9342
+ [crudId]="resolvedCrudId()"
9343
+ [metadata]="effectiveMetadata()"
9344
+ [enableCustomization]="effectiveEnableCustomization()"
9345
+ [tableConfigPersistenceStrategy]="tableConfigPersistenceStrategy"
9346
+ [context]="context"
9347
+ (rowClick)="rowClick.emit($event)"
9348
+ (rowAction)="rowAction.emit($event)"
9349
+ (selectionChange)="selectionChange.emit($event)"
9350
+ (drawerOpen)="drawerOpen.emit($event)"
9351
+ (drawerClose)="drawerClose.emit()"
9352
+ (afterOpen)="afterOpen.emit($event)"
9353
+ (afterClose)="afterClose.emit()"
9354
+ (kpiCardClick)="handleKpiCardClick($event)"
9355
+ (afterSave)="afterSave.emit($event)"
9356
+ (afterDelete)="afterDelete.emit($event)"
9357
+ (error)="error.emit($event)"
9358
+ ></praxis-crud>
9359
+ </section>
9360
+ }
9361
+ </div>
9362
+ `, styles: [":host{display:block;width:100%;min-height:100%;box-sizing:border-box}.praxis-resource-page{width:100%;display:flex;flex-direction:column;gap:20px;padding:var(--praxis-page-padding, 24px);box-sizing:border-box;max-width:1540px;margin:0 auto}.praxis-resource-page__header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;flex-wrap:wrap}.praxis-resource-page__header-main{min-width:0;display:flex;flex-direction:column}.praxis-resource-page__domain-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px;align-self:flex-start;background:var(--praxis-domain-badge-bg, rgba(255, 255, 255, .08));color:var(--praxis-domain-badge-color, inherit);border:1px solid var(--praxis-domain-badge-border, rgba(255, 255, 255, .1))}.praxis-resource-page__domain-badge span.material-symbols-outlined{font-size:14px}.praxis-resource-page__title{margin:0 0 6px;font-size:var(--praxis-page-title-size, 2rem);font-weight:800;letter-spacing:-.02em;color:var(--praxis-page-title-color, var(--foreground, inherit));line-height:1.2}.praxis-resource-page__subtitle{margin:0;font-size:var(--praxis-page-subtitle-size, .9rem);color:var(--muted-foreground, #94a3b8);max-width:800px;line-height:1.5}.praxis-resource-page__crud-surface{width:100%;border-radius:var(--praxis-surface-radius, 16px);overflow:hidden}\n"] }]
9363
+ }], propDecorators: { resource: [{
9364
+ type: Input
9365
+ }], resourcePath: [{
9366
+ type: Input
9367
+ }], crudId: [{
9368
+ type: Input
9369
+ }], metadata: [{
9370
+ type: Input
9371
+ }], title: [{
9372
+ type: Input
9373
+ }], subtitle: [{
9374
+ type: Input
9375
+ }], domain: [{
9376
+ type: Input
9377
+ }], scopeBar: [{
9378
+ type: Input
9379
+ }], scopeItems: [{
9380
+ type: Input
9381
+ }], defaultScopeId: [{
9382
+ type: Input
9383
+ }], enableCustomization: [{
9384
+ type: Input
9385
+ }], tableConfigPersistenceStrategy: [{
9386
+ type: Input
9387
+ }], context: [{
9388
+ type: Input
9389
+ }], actions: [{
9390
+ type: Input
9391
+ }], filterCriteria: [{
9392
+ type: Input
9393
+ }], rowClick: [{
9394
+ type: Output
9395
+ }], rowAction: [{
9396
+ type: Output
9397
+ }], rowDoubleClick: [{
9398
+ type: Output
9399
+ }], selectionChange: [{
9400
+ type: Output
9401
+ }], drawerOpen: [{
9402
+ type: Output
9403
+ }], drawerClose: [{
9404
+ type: Output
9405
+ }], afterOpen: [{
9406
+ type: Output
9407
+ }], afterClose: [{
9408
+ type: Output
9409
+ }], kpiCardClick: [{
9410
+ type: Output
9411
+ }], afterSave: [{
9412
+ type: Output
9413
+ }], afterDelete: [{
9414
+ type: Output
9415
+ }], error: [{
9416
+ type: Output
9417
+ }] } });
9418
+
8244
9419
  /*
8245
9420
  * Public API Surface of praxis-crud
8246
9421
  */
@@ -8249,4 +9424,4 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
8249
9424
  * Generated bundle index. Do not edit.
8250
9425
  */
8251
9426
 
8252
- export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudMetadataEditorComponent, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_AUTHORING_MANIFEST, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, PraxisCrudWidgetConfigEditor, assertCrudMetadata, createCrudAuthoringDocument, findCrudAction, normalizeCrudAuthoringDocument, openCrudMetadataEditor, parseLegacyOrCrudDocument, providePraxisCrudMetadata, serializeCrudAuthoringDocument, validateCrudAuthoringDocument };
9427
+ export { CRUD_AI_CAPABILITIES, CrudLauncherService, CrudMetadataEditorComponent, CrudPageHeaderComponent, DialogService, DynamicFormDialogHostComponent, PRAXIS_CRUD_AUTHORING_MANIFEST, PRAXIS_CRUD_COMPONENT_METADATA, PraxisCrudComponent, PraxisCrudWidgetConfigEditor, PraxisKpiBandComponent, PraxisResourcePage, assertCrudMetadata, createCrudAuthoringDocument, findCrudAction, formatKpiValue, normalizeCrudAuthoringDocument, openCrudMetadataEditor, parseLegacyOrCrudDocument, providePraxisCrudMetadata, serializeCrudAuthoringDocument, validateCrudAuthoringDocument };