@praxisui/crud 9.0.0-beta.9 → 9.0.0-beta.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -9
- package/ai/component-registry.json +12227 -0
- package/fesm2022/praxisui-crud.mjs +937 -116
- package/package.json +12 -8
- package/types/praxisui-crud-drawer-adapter.d.ts +3 -1
- package/types/praxisui-crud.d.ts +78 -3
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, Injector, ViewChild, Output, Input, Inject, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
2
|
+
import { Injectable, InjectionToken, inject, input, signal, computed, effect, ChangeDetectionStrategy, Component, EventEmitter, DestroyRef, ChangeDetectorRef, Injector, ViewChild, Output, Input, 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
8
|
import * as i2$1 from '@praxisui/core';
|
|
9
|
-
import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, GLOBAL_SURFACE_SERVICE, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
|
|
9
|
+
import { ASYNC_CONFIG_STORAGE, GlobalConfigService, CrudOperationResolutionService, fillUndefined, SETTINGS_PANEL_DATA, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, createDefaultTableConfig, EnterpriseRuntimeContextService, GLOBAL_SURFACE_SERVICE, SurfaceOutletRegistryService, ComponentKeyService, ResourceDiscoveryService, ResourceActionOpenAdapterService, ResourceSurfaceOpenAdapterService, translateUnavailableWorkflowMessage, EmptyStateCardComponent, RESOURCE_DISCOVERY_I18N_CONFIG, PraxisIconButtonComponent, PraxisResourceIdentityComponent, GenericCrudService, ComponentMetadataRegistry } from '@praxisui/core';
|
|
10
10
|
import { SettingsPanelService } from '@praxisui/settings-panel';
|
|
11
11
|
import { PraxisTableInlineAuthoringEditorComponent, PraxisTable } from '@praxisui/table';
|
|
12
12
|
import { ConfirmDialogComponent } from '@praxisui/dynamic-fields';
|
|
13
|
+
import { take, filter } from 'rxjs/operators';
|
|
13
14
|
import * as i1 from '@angular/material/dialog';
|
|
14
15
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
|
15
16
|
export { MAT_DIALOG_DATA as DIALOG_DATA } from '@angular/material/dialog';
|
|
@@ -34,7 +35,6 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
|
34
35
|
import * as i10 from '@angular/material/tabs';
|
|
35
36
|
import { MatTabsModule } from '@angular/material/tabs';
|
|
36
37
|
import { PraxisDynamicForm } from '@praxisui/dynamic-form';
|
|
37
|
-
import { filter, take } from 'rxjs/operators';
|
|
38
38
|
|
|
39
39
|
class DialogService {
|
|
40
40
|
matDialog;
|
|
@@ -68,6 +68,35 @@ function getCrudDrawerAdapterToken() {
|
|
|
68
68
|
}
|
|
69
69
|
const CRUD_DRAWER_ADAPTER = getCrudDrawerAdapterToken();
|
|
70
70
|
|
|
71
|
+
const DEFAULT_CRUD_DRAWER_MODAL_CONFIG = {
|
|
72
|
+
minWidth: 'min(100vw, 520px)',
|
|
73
|
+
width: 'min(760px, 58vw)',
|
|
74
|
+
height: '100dvh',
|
|
75
|
+
maxWidth: '100vw',
|
|
76
|
+
maxHeight: '100dvh',
|
|
77
|
+
position: { right: '0', top: '0' },
|
|
78
|
+
};
|
|
79
|
+
function isCrudDebugEnabled$1() {
|
|
80
|
+
try {
|
|
81
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (typeof localStorage !== 'undefined') {
|
|
85
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function debugCrudLauncher(message, ...data) {
|
|
92
|
+
if (!isCrudDebugEnabled$1()) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
console.debug(message, ...data);
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
}
|
|
71
100
|
class CrudLauncherService {
|
|
72
101
|
router = inject(Router);
|
|
73
102
|
dialog = inject(DialogService);
|
|
@@ -85,8 +114,9 @@ class CrudLauncherService {
|
|
|
85
114
|
async launch(action, row, metadata, componentKeyId, drawerCallbacks, runtime) {
|
|
86
115
|
// Carregar overrides de CRUD (se houver) e mesclar em uma cópia local
|
|
87
116
|
const merged = await this.mergeCrudOverrides(metadata, action, componentKeyId || undefined);
|
|
117
|
+
merged.action = this.normalizeActionForLaunch(merged.action);
|
|
88
118
|
const mode = this.resolveOpenMode(merged.action, merged.metadata);
|
|
89
|
-
|
|
119
|
+
debugCrudLauncher('[CRUD:Launcher] mode=', mode, 'action=', action);
|
|
90
120
|
if (mode === 'route') {
|
|
91
121
|
if (!merged.action.route) {
|
|
92
122
|
throw new Error(`Route not provided for action ${merged.action.action}`);
|
|
@@ -95,22 +125,6 @@ class CrudLauncherService {
|
|
|
95
125
|
await this.router.navigateByUrl(url);
|
|
96
126
|
return { mode };
|
|
97
127
|
}
|
|
98
|
-
if (mode === 'drawer' && this.drawerAdapter) {
|
|
99
|
-
const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
|
|
100
|
-
const inputs = this.mapInputs(actionForLaunch, row, merged.metadata, runtime);
|
|
101
|
-
const idField = merged.metadata.resource?.idField ?? 'id';
|
|
102
|
-
if (row && inputs[idField] === undefined && row[idField] !== undefined) {
|
|
103
|
-
inputs[idField] = row[idField];
|
|
104
|
-
}
|
|
105
|
-
await Promise.resolve(this.drawerAdapter.open({
|
|
106
|
-
action: actionForLaunch,
|
|
107
|
-
metadata: merged.metadata,
|
|
108
|
-
inputs,
|
|
109
|
-
onClose: drawerCallbacks?.onClose,
|
|
110
|
-
onResult: drawerCallbacks?.onResult,
|
|
111
|
-
}));
|
|
112
|
-
return { mode };
|
|
113
|
-
}
|
|
114
128
|
const actionForLaunch = this.resolveActionForLaunch(merged.action, merged.metadata);
|
|
115
129
|
if (!actionForLaunch.formId) {
|
|
116
130
|
throw new Error(`formId not provided for action ${actionForLaunch.action}`);
|
|
@@ -123,18 +137,33 @@ class CrudLauncherService {
|
|
|
123
137
|
row[idField] !== undefined) {
|
|
124
138
|
inputs[idField] = row[idField];
|
|
125
139
|
}
|
|
140
|
+
const resourceIdentity = actionForLaunch.action === 'edit'
|
|
141
|
+
? runtime?.resourceIdentity ?? null
|
|
142
|
+
: null;
|
|
143
|
+
if (mode === 'drawer' && this.drawerAdapter) {
|
|
144
|
+
await Promise.resolve(this.drawerAdapter.open({
|
|
145
|
+
action: actionForLaunch,
|
|
146
|
+
metadata: merged.metadata,
|
|
147
|
+
inputs,
|
|
148
|
+
resourceIdentity,
|
|
149
|
+
onClose: drawerCallbacks?.onClose,
|
|
150
|
+
onResult: drawerCallbacks?.onResult,
|
|
151
|
+
}));
|
|
152
|
+
return { mode };
|
|
153
|
+
}
|
|
126
154
|
const modalCfg = { ...(merged.metadata.defaults?.modal || {}) };
|
|
127
|
-
|
|
155
|
+
debugCrudLauncher('[CRUD:Launcher] opening dialog with:', {
|
|
128
156
|
action: merged.action.action,
|
|
129
157
|
formId: actionForLaunch.formId,
|
|
130
158
|
inputs,
|
|
131
159
|
modalCfg,
|
|
132
160
|
resourcePath: merged.metadata.resource?.path ?? merged.metadata.table?.resourcePath,
|
|
133
161
|
});
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
162
|
+
const drawerMode = mode === 'drawer';
|
|
163
|
+
const dialogPosition = drawerMode
|
|
164
|
+
? (modalCfg.position ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.position)
|
|
165
|
+
: modalCfg.position;
|
|
166
|
+
const panelClasses = mergeClassList(['pfx-dialog-pane', drawerMode ? 'pfx-drawer-pane' : 'pfx-dialog-frosted'], modalCfg.panelClass);
|
|
138
167
|
// Backdrop style presets
|
|
139
168
|
const backdropClasses = [];
|
|
140
169
|
const style = modalCfg.backdropStyle;
|
|
@@ -147,20 +176,56 @@ class CrudLauncherService {
|
|
|
147
176
|
else if (style === 'transparent') {
|
|
148
177
|
backdropClasses.push('pfx-transparent-backdrop');
|
|
149
178
|
}
|
|
150
|
-
|
|
151
|
-
backdropClasses.push(modalCfg.backdropClass);
|
|
152
|
-
}
|
|
179
|
+
const mergedBackdropClasses = mergeClassList(backdropClasses, modalCfg.backdropClass);
|
|
153
180
|
const ref = await this.dialog.openAsync(() => Promise.resolve().then(function () { return dynamicFormDialogHost_component; }).then((m) => m.DynamicFormDialogHostComponent), {
|
|
154
181
|
...modalCfg,
|
|
155
182
|
panelClass: panelClasses,
|
|
156
|
-
backdropClass:
|
|
183
|
+
backdropClass: mergedBackdropClasses,
|
|
157
184
|
autoFocus: modalCfg.autoFocus ?? true,
|
|
158
185
|
restoreFocus: modalCfg.restoreFocus ?? true,
|
|
159
|
-
minWidth:
|
|
160
|
-
|
|
186
|
+
minWidth: drawerMode
|
|
187
|
+
? (modalCfg.minWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.minWidth)
|
|
188
|
+
: '360px',
|
|
189
|
+
width: drawerMode
|
|
190
|
+
? (modalCfg.width ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.width)
|
|
191
|
+
: modalCfg.width,
|
|
192
|
+
height: drawerMode
|
|
193
|
+
? (modalCfg.height ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.height)
|
|
194
|
+
: modalCfg.height,
|
|
195
|
+
maxWidth: drawerMode
|
|
196
|
+
? (modalCfg.maxWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.maxWidth)
|
|
197
|
+
: '95vw',
|
|
198
|
+
maxHeight: drawerMode
|
|
199
|
+
? (modalCfg.maxHeight ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.maxHeight)
|
|
200
|
+
: modalCfg.maxHeight,
|
|
201
|
+
position: dialogPosition,
|
|
161
202
|
ariaLabelledBy: 'crudDialogTitle',
|
|
162
|
-
data: {
|
|
203
|
+
data: {
|
|
204
|
+
action: actionForLaunch,
|
|
205
|
+
row,
|
|
206
|
+
metadata: merged.metadata,
|
|
207
|
+
inputs,
|
|
208
|
+
presentation: drawerMode ? 'drawer' : 'modal',
|
|
209
|
+
dialogPosition,
|
|
210
|
+
resourceIdentity,
|
|
211
|
+
},
|
|
163
212
|
});
|
|
213
|
+
if (drawerMode) {
|
|
214
|
+
ref
|
|
215
|
+
.afterClosed()
|
|
216
|
+
.pipe(take(1))
|
|
217
|
+
.subscribe((closedValue) => {
|
|
218
|
+
const result = toCrudDrawerResult(closedValue);
|
|
219
|
+
drawerCallbacks?.onClose?.();
|
|
220
|
+
if (result?.type) {
|
|
221
|
+
drawerCallbacks?.onResult?.(result);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
drawerCallbacks?.onResult?.({ type: 'close' });
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
return { mode, ref };
|
|
228
|
+
}
|
|
164
229
|
return { mode, ref };
|
|
165
230
|
}
|
|
166
231
|
resolveOpenMode(action, metadata) {
|
|
@@ -168,12 +233,17 @@ class CrudLauncherService {
|
|
|
168
233
|
const local = action.openMode ?? metadata.defaults?.openMode;
|
|
169
234
|
if (local)
|
|
170
235
|
return local;
|
|
171
|
-
// Global fallback (action-specific, then general)
|
|
236
|
+
// Global fallback (action-specific, then general). A canonical CRUD
|
|
237
|
+
// operation without an explicit route can still be materialized through
|
|
238
|
+
// the inferred form contract, so it must not fall through to a route that
|
|
239
|
+
// does not exist.
|
|
172
240
|
try {
|
|
173
241
|
const globalCrud = this.global.getCrud();
|
|
174
242
|
const actionName = action.action;
|
|
175
243
|
const globalMode = (actionName && globalCrud?.actionDefaults?.[actionName]?.openMode) ?? globalCrud?.defaults?.openMode;
|
|
176
|
-
let resolved = globalMode ??
|
|
244
|
+
let resolved = globalMode ?? (this.isCanonicalCrudAction(action.action) && !action.route
|
|
245
|
+
? 'drawer'
|
|
246
|
+
: 'route');
|
|
177
247
|
// Safety: if modal/drawer but there is no formId and the action cannot be inferred, degrade to route.
|
|
178
248
|
if ((resolved === 'modal' || resolved === 'drawer') &&
|
|
179
249
|
!action.formId &&
|
|
@@ -248,6 +318,9 @@ class CrudLauncherService {
|
|
|
248
318
|
if (resolved.apiUrlEntry != null) {
|
|
249
319
|
inputs['apiUrlEntry'] = resolved.apiUrlEntry;
|
|
250
320
|
}
|
|
321
|
+
if (resolved.layoutPolicy != null) {
|
|
322
|
+
inputs['layoutPolicy'] = resolved.layoutPolicy;
|
|
323
|
+
}
|
|
251
324
|
if (action.form?.initialValue != null) {
|
|
252
325
|
inputs['initialValue'] = action.form.initialValue;
|
|
253
326
|
}
|
|
@@ -257,6 +330,7 @@ class CrudLauncherService {
|
|
|
257
330
|
return inputs;
|
|
258
331
|
}
|
|
259
332
|
resolveActionForLaunch(action, metadata) {
|
|
333
|
+
action = this.normalizeActionForLaunch(action);
|
|
260
334
|
if (action.formId) {
|
|
261
335
|
return action;
|
|
262
336
|
}
|
|
@@ -264,29 +338,61 @@ class CrudLauncherService {
|
|
|
264
338
|
return action;
|
|
265
339
|
}
|
|
266
340
|
const formId = this.buildInferredFormId(action, metadata);
|
|
267
|
-
return formId
|
|
341
|
+
return formId
|
|
342
|
+
? {
|
|
343
|
+
...action,
|
|
344
|
+
formId,
|
|
345
|
+
form: {
|
|
346
|
+
...(action.form || {}),
|
|
347
|
+
layoutPolicy: action.form?.layoutPolicy ?? this.defaultInferredLayoutPolicy(action),
|
|
348
|
+
},
|
|
349
|
+
}
|
|
350
|
+
: action;
|
|
351
|
+
}
|
|
352
|
+
resolveSubmitUrlTemplate(submitUrl, row, metadata) {
|
|
353
|
+
const template = String(submitUrl || '').trim();
|
|
354
|
+
if (!template || !template.includes('{')) {
|
|
355
|
+
return template;
|
|
356
|
+
}
|
|
357
|
+
const idField = String(metadata.resource?.idField ?? 'id');
|
|
358
|
+
const rowId = row?.[idField];
|
|
359
|
+
if (rowId == null) {
|
|
360
|
+
return template;
|
|
361
|
+
}
|
|
362
|
+
const encodedId = encodeURIComponent(String(rowId));
|
|
363
|
+
const idFieldPattern = new RegExp(`\\{${escapeRegExp(idField)}\\}`, 'g');
|
|
364
|
+
return template
|
|
365
|
+
.replace(idFieldPattern, encodedId)
|
|
366
|
+
.replace(/\{id\}/g, encodedId)
|
|
367
|
+
.replace(/\{resourceId\}/g, encodedId);
|
|
268
368
|
}
|
|
269
369
|
resolveActionFormContract(action, row, metadata, runtime) {
|
|
270
370
|
if (this.isExplicitCrudAction(action)) {
|
|
271
371
|
return {
|
|
272
372
|
schemaUrl: action.form?.schemaUrl ?? null,
|
|
273
|
-
submitUrl: action.form?.submitUrl
|
|
373
|
+
submitUrl: action.form?.submitUrl
|
|
374
|
+
? this.resolveSubmitUrlTemplate(action.form.submitUrl, row, metadata)
|
|
375
|
+
: null,
|
|
274
376
|
submitMethod: action.form?.submitMethod ?? null,
|
|
275
377
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
276
378
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
379
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
277
380
|
};
|
|
278
381
|
}
|
|
279
382
|
if (!this.isCanonicalCrudAction(action.action)) {
|
|
280
383
|
return {
|
|
281
384
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
282
385
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
386
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
283
387
|
};
|
|
284
388
|
}
|
|
285
389
|
const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
|
|
390
|
+
const schemaResourcePath = String(metadata.resource?.schemaPath || '').trim() || null;
|
|
286
391
|
if (!resourcePath) {
|
|
287
392
|
return {
|
|
288
393
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
289
394
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
395
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
290
396
|
};
|
|
291
397
|
}
|
|
292
398
|
const idField = String(metadata.resource?.idField ?? 'id');
|
|
@@ -294,6 +400,7 @@ class CrudLauncherService {
|
|
|
294
400
|
const resolved = this.operationResolver.resolve({
|
|
295
401
|
operation: action.action,
|
|
296
402
|
resourcePath,
|
|
403
|
+
schemaResourcePath,
|
|
297
404
|
resourceId: resourceId ?? null,
|
|
298
405
|
capabilities: runtime?.capabilities ?? null,
|
|
299
406
|
links: runtime?.links ?? null,
|
|
@@ -301,14 +408,15 @@ class CrudLauncherService {
|
|
|
301
408
|
endpointKey: action.form?.apiEndpointKey ??
|
|
302
409
|
metadata.resource?.endpointKey ??
|
|
303
410
|
undefined,
|
|
304
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
411
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
305
412
|
});
|
|
306
413
|
return {
|
|
307
414
|
schemaUrl: resolved?.schemaUrl ?? null,
|
|
308
415
|
submitUrl: resolved?.submitUrl ?? null,
|
|
309
416
|
submitMethod: resolved?.submitMethod ?? null,
|
|
310
417
|
apiEndpointKey: action.form?.apiEndpointKey ?? metadata.resource?.endpointKey ?? null,
|
|
311
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
418
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
419
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
312
420
|
};
|
|
313
421
|
}
|
|
314
422
|
resolveRuntimeContract(action, row, metadata, runtime) {
|
|
@@ -327,9 +435,20 @@ class CrudLauncherService {
|
|
|
327
435
|
return `${sanitizedResource || 'crud'}-${String(action.action || '').trim().toLowerCase()}`;
|
|
328
436
|
}
|
|
329
437
|
isCanonicalCrudAction(actionName) {
|
|
330
|
-
const normalized =
|
|
438
|
+
const normalized = this.normalizeCrudOperationName(actionName);
|
|
331
439
|
return normalized === 'create' || normalized === 'view' || normalized === 'edit' || normalized === 'delete';
|
|
332
440
|
}
|
|
441
|
+
normalizeActionForLaunch(action) {
|
|
442
|
+
const normalized = this.normalizeCrudOperationName(action.action);
|
|
443
|
+
return normalized === action.action ? action : { ...action, action: normalized };
|
|
444
|
+
}
|
|
445
|
+
normalizeCrudOperationName(actionName) {
|
|
446
|
+
const normalized = String(actionName || '').trim().toLowerCase();
|
|
447
|
+
if (['add', 'novo', 'new', 'incluir', 'inserir'].includes(normalized)) {
|
|
448
|
+
return 'create';
|
|
449
|
+
}
|
|
450
|
+
return normalized;
|
|
451
|
+
}
|
|
333
452
|
isExplicitCrudAction(action) {
|
|
334
453
|
if (action.mode === 'explicit') {
|
|
335
454
|
return true;
|
|
@@ -338,6 +457,19 @@ class CrudLauncherService {
|
|
|
338
457
|
String(action.form?.submitUrl || '').trim() ||
|
|
339
458
|
String(action.form?.submitMethod || '').trim());
|
|
340
459
|
}
|
|
460
|
+
defaultInferredLayoutPolicy(action) {
|
|
461
|
+
const operation = this.normalizeCrudOperationName(action.action);
|
|
462
|
+
if (operation !== 'create' && operation !== 'edit') {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
source: 'schema',
|
|
467
|
+
intent: 'command',
|
|
468
|
+
preset: 'groupedCommand',
|
|
469
|
+
persistence: 'transient',
|
|
470
|
+
schemaType: 'request',
|
|
471
|
+
};
|
|
472
|
+
}
|
|
341
473
|
async mergeCrudOverrides(metadata, action, componentKeyId) {
|
|
342
474
|
try {
|
|
343
475
|
if (!componentKeyId)
|
|
@@ -392,6 +524,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
392
524
|
type: Injectable,
|
|
393
525
|
args: [{ providedIn: 'root' }]
|
|
394
526
|
}] });
|
|
527
|
+
function mergeClassList(base, extra) {
|
|
528
|
+
if (!extra) {
|
|
529
|
+
return base;
|
|
530
|
+
}
|
|
531
|
+
if (typeof extra === 'string') {
|
|
532
|
+
return [...base, extra];
|
|
533
|
+
}
|
|
534
|
+
if (Array.isArray(extra)) {
|
|
535
|
+
return [...base, ...extra];
|
|
536
|
+
}
|
|
537
|
+
return [
|
|
538
|
+
...base,
|
|
539
|
+
...Object.keys(extra).filter((className) => !!extra[className]),
|
|
540
|
+
];
|
|
541
|
+
}
|
|
542
|
+
function escapeRegExp(value) {
|
|
543
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
544
|
+
}
|
|
545
|
+
function toCrudDrawerResult(value) {
|
|
546
|
+
if (!value || typeof value !== 'object') {
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
const result = value;
|
|
550
|
+
return result.type === 'save' || result.type === 'delete' || result.type === 'close'
|
|
551
|
+
? result
|
|
552
|
+
: undefined;
|
|
553
|
+
}
|
|
395
554
|
|
|
396
555
|
const DOCUMENT_KIND = 'praxis.crud.editor';
|
|
397
556
|
const DOCUMENT_VERSION = 1;
|
|
@@ -510,10 +669,15 @@ function normalizeCrudMetadata(metadata) {
|
|
|
510
669
|
if (base.resource && typeof base.resource === 'object') {
|
|
511
670
|
normalized.resource = stripUndefinedShallow({
|
|
512
671
|
path: trimString(base.resource.path) || undefined,
|
|
672
|
+
schemaPath: trimString(base.resource.schemaPath) || undefined,
|
|
513
673
|
idField: typeof base.resource.idField === 'string'
|
|
514
674
|
? trimString(base.resource.idField) || undefined
|
|
515
675
|
: base.resource.idField,
|
|
676
|
+
title: trimString(base.resource.title) || undefined,
|
|
677
|
+
label: trimString(base.resource.label) || undefined,
|
|
678
|
+
formTitle: trimString(base.resource.formTitle) || undefined,
|
|
516
679
|
endpointKey: trimString(base.resource.endpointKey) || undefined,
|
|
680
|
+
apiUrlEntry: base.resource.apiUrlEntry || undefined,
|
|
517
681
|
});
|
|
518
682
|
}
|
|
519
683
|
if (base.defaults && typeof base.defaults === 'object') {
|
|
@@ -564,6 +728,7 @@ function normalizeCrudAction(action) {
|
|
|
564
728
|
apiEndpointKey: trimString(action.form.apiEndpointKey) || undefined,
|
|
565
729
|
apiUrlEntry: action.form.apiUrlEntry || undefined,
|
|
566
730
|
initialValue: normalizeInitialValue(action.form.initialValue),
|
|
731
|
+
layoutPolicy: action.form.layoutPolicy ?? undefined,
|
|
567
732
|
}
|
|
568
733
|
: undefined,
|
|
569
734
|
});
|
|
@@ -721,11 +886,17 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
721
886
|
'crud.emptyState.title': 'Conecte o CRUD a um recurso',
|
|
722
887
|
'crud.emptyState.description': 'Informe os metadados (resourcePath / schema) ou forneça metadata.data para habilitar a tabela e as ações.',
|
|
723
888
|
'crud.emptyState.primaryAction': 'Configurar metadados',
|
|
889
|
+
'crud.table.emptyState.initial.title': 'Sem registros em {label}',
|
|
890
|
+
'crud.table.emptyState.initial.titleFallback': 'Nenhum registro disponível.',
|
|
891
|
+
'crud.table.emptyState.initial.descriptionWithAction': 'Use a ação principal para adicionar o primeiro registro quando houver informações para cadastrar.',
|
|
892
|
+
'crud.table.emptyState.filtered.title': 'Nenhum resultado encontrado.',
|
|
893
|
+
'crud.table.emptyState.filtered.description': 'Revise os filtros ou ajuste o termo de busca.',
|
|
724
894
|
'crud.preferences.resetSuccess': 'Overrides de CRUD redefinidos',
|
|
725
895
|
'crud.actions.create': 'Adicionar',
|
|
726
896
|
'crud.actions.view': 'Ver',
|
|
727
897
|
'crud.actions.edit': 'Editar',
|
|
728
898
|
'crud.actions.delete': 'Excluir',
|
|
899
|
+
'crud.dialog.recordContextLabel': 'Registro em edição',
|
|
729
900
|
'crud.delete.confirmMessage': 'Esta ação não pode ser desfeita. Deseja continuar?',
|
|
730
901
|
'crud.delete.cancel': 'Cancelar',
|
|
731
902
|
},
|
|
@@ -733,11 +904,17 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
733
904
|
'crud.emptyState.title': 'Connect CRUD to a resource',
|
|
734
905
|
'crud.emptyState.description': 'Provide metadata (resourcePath / schema) or metadata.data to enable the table and actions.',
|
|
735
906
|
'crud.emptyState.primaryAction': 'Configure metadata',
|
|
907
|
+
'crud.table.emptyState.initial.title': 'No records in {label}',
|
|
908
|
+
'crud.table.emptyState.initial.titleFallback': 'No records available.',
|
|
909
|
+
'crud.table.emptyState.initial.descriptionWithAction': 'Use the primary action to add the first record when there is information to register.',
|
|
910
|
+
'crud.table.emptyState.filtered.title': 'No results found.',
|
|
911
|
+
'crud.table.emptyState.filtered.description': 'Review the filters or adjust the search term.',
|
|
736
912
|
'crud.preferences.resetSuccess': 'CRUD overrides reset',
|
|
737
913
|
'crud.actions.create': 'Add',
|
|
738
914
|
'crud.actions.view': 'View',
|
|
739
915
|
'crud.actions.edit': 'Edit',
|
|
740
916
|
'crud.actions.delete': 'Delete',
|
|
917
|
+
'crud.dialog.recordContextLabel': 'Record being edited',
|
|
741
918
|
'crud.delete.confirmMessage': 'This action cannot be undone. Do you want to continue?',
|
|
742
919
|
'crud.delete.cancel': 'Cancel',
|
|
743
920
|
},
|
|
@@ -772,8 +949,9 @@ function resolveCrudRuntimeFallback(key, locale) {
|
|
|
772
949
|
return getCrudRuntimeDictionary(locale || 'pt-BR')?.[key];
|
|
773
950
|
}
|
|
774
951
|
function translateCrudRuntimeText(i18n, key, fallback, params, locale) {
|
|
775
|
-
const
|
|
776
|
-
|
|
952
|
+
const effectiveLocale = locale || i18n.getLocale?.() || i18n.getFallbackLocale?.() || 'pt-BR';
|
|
953
|
+
const runtimeFallback = resolveCrudRuntimeFallback(key, effectiveLocale)
|
|
954
|
+
|| resolveCrudRuntimeFallback(key, i18n.getFallbackLocale?.())
|
|
777
955
|
|| fallback
|
|
778
956
|
|| key;
|
|
779
957
|
return i18n.t(key, params, runtimeFallback, PRAXIS_CRUD_RUNTIME_I18N_NAMESPACE);
|
|
@@ -3467,6 +3645,8 @@ class PraxisCrudComponent {
|
|
|
3467
3645
|
componentInstanceId;
|
|
3468
3646
|
context;
|
|
3469
3647
|
enableCustomization = false;
|
|
3648
|
+
/** Capability publica exigida para authoring governado do CRUD e da tabela interna. */
|
|
3649
|
+
authoringCapability = null;
|
|
3470
3650
|
configureRequested = new EventEmitter();
|
|
3471
3651
|
afterOpen = new EventEmitter();
|
|
3472
3652
|
afterClose = new EventEmitter();
|
|
@@ -3491,6 +3671,7 @@ class PraxisCrudComponent {
|
|
|
3491
3671
|
table;
|
|
3492
3672
|
storage = inject(ASYNC_CONFIG_STORAGE);
|
|
3493
3673
|
settingsPanel = inject(SettingsPanelService);
|
|
3674
|
+
enterpriseRuntimeContext = inject(EnterpriseRuntimeContextService, { optional: true });
|
|
3494
3675
|
snack = inject(MatSnackBar);
|
|
3495
3676
|
dialog = inject(DialogService);
|
|
3496
3677
|
i18n = inject(PraxisI18nService);
|
|
@@ -3498,6 +3679,7 @@ class PraxisCrudComponent {
|
|
|
3498
3679
|
resourceDiscoveryInstance;
|
|
3499
3680
|
actionOpenAdapterInstance;
|
|
3500
3681
|
surfaceOpenAdapterInstance;
|
|
3682
|
+
materializedRefreshSequence = 0;
|
|
3501
3683
|
global = (() => {
|
|
3502
3684
|
try {
|
|
3503
3685
|
return inject(GlobalConfigService);
|
|
@@ -3507,6 +3689,7 @@ class PraxisCrudComponent {
|
|
|
3507
3689
|
}
|
|
3508
3690
|
})();
|
|
3509
3691
|
surfaceService = inject(GLOBAL_SURFACE_SERVICE, { optional: true });
|
|
3692
|
+
surfaceOutlets = inject(SurfaceOutletRegistryService);
|
|
3510
3693
|
componentKeys = inject(ComponentKeyService);
|
|
3511
3694
|
route = (() => {
|
|
3512
3695
|
try {
|
|
@@ -3523,10 +3706,23 @@ class PraxisCrudComponent {
|
|
|
3523
3706
|
lastAppliedResourceIdentity = null;
|
|
3524
3707
|
tableCollectionLinks = null;
|
|
3525
3708
|
collectionCapabilities = null;
|
|
3709
|
+
activeAuthoringPanelRef;
|
|
3710
|
+
constructor() {
|
|
3711
|
+
this.enterpriseRuntimeContext?.contextChanges$
|
|
3712
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3713
|
+
.subscribe(() => {
|
|
3714
|
+
if (!this.isCustomizationAvailable()) {
|
|
3715
|
+
this.activeAuthoringPanelRef?.close();
|
|
3716
|
+
this.activeAuthoringPanelRef = undefined;
|
|
3717
|
+
}
|
|
3718
|
+
this.cdr.markForCheck();
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
3526
3721
|
collectionCapabilitiesRequestHref = null;
|
|
3527
3722
|
collectionCapabilitiesResolvedHref = null;
|
|
3528
3723
|
collectionCapabilitiesRequestSeq = 0;
|
|
3529
3724
|
currentAuthoringDocument;
|
|
3725
|
+
selectedRow = null;
|
|
3530
3726
|
getResourceDiscovery() {
|
|
3531
3727
|
const assigned = this.resourceDiscovery;
|
|
3532
3728
|
return assigned ?? (this.resourceDiscoveryInstance ??= this.injector.get(ResourceDiscoveryService));
|
|
@@ -3553,15 +3749,41 @@ class PraxisCrudComponent {
|
|
|
3553
3749
|
catch { }
|
|
3554
3750
|
}
|
|
3555
3751
|
onTableRowClick(event) {
|
|
3752
|
+
const row = this.extractRowFromEvent(event);
|
|
3753
|
+
if (row) {
|
|
3754
|
+
this.selectedRow = row;
|
|
3755
|
+
}
|
|
3556
3756
|
this.rowClick.emit(event);
|
|
3557
3757
|
}
|
|
3558
3758
|
onTableSelectionChange(event) {
|
|
3759
|
+
this.selectedRow = this.extractSelectedRowFromEvent(event);
|
|
3559
3760
|
this.selectionChange.emit(event);
|
|
3560
3761
|
}
|
|
3762
|
+
async onBulkAction(event) {
|
|
3763
|
+
const action = String(event?.action || '').trim();
|
|
3764
|
+
if (!action) {
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
const row = this.extractSelectedRowFromBulkAction(event);
|
|
3768
|
+
if (row) {
|
|
3769
|
+
this.selectedRow = row;
|
|
3770
|
+
}
|
|
3771
|
+
await this.onAction(action, row ?? undefined, event);
|
|
3772
|
+
}
|
|
3773
|
+
async onToolbarAction(event) {
|
|
3774
|
+
const action = String(event?.action || '').trim();
|
|
3775
|
+
if (!action)
|
|
3776
|
+
return;
|
|
3777
|
+
const row = this.extractSelectedRowFromToolbarAction(event);
|
|
3778
|
+
if (row)
|
|
3779
|
+
this.selectedRow = row;
|
|
3780
|
+
await this.onAction(action, row ?? undefined, event);
|
|
3781
|
+
}
|
|
3561
3782
|
ngOnChanges(changes) {
|
|
3562
3783
|
if (!changes['metadata'] && !changes['context']) {
|
|
3563
3784
|
return;
|
|
3564
3785
|
}
|
|
3786
|
+
this.materializedRefreshSequence += 1;
|
|
3565
3787
|
try {
|
|
3566
3788
|
const parsed = typeof this.metadata === 'string'
|
|
3567
3789
|
? JSON.parse(this.metadata)
|
|
@@ -3595,33 +3817,46 @@ class PraxisCrudComponent {
|
|
|
3595
3817
|
async onAction(action, row, runtimeEvent) {
|
|
3596
3818
|
try {
|
|
3597
3819
|
document.activeElement?.blur();
|
|
3598
|
-
|
|
3820
|
+
const normalizedAction = this.normalizeCrudActionName(action);
|
|
3821
|
+
const contextualRow = row ?? this.resolveSelectedRowForAction(normalizedAction);
|
|
3822
|
+
let actionMeta = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3823
|
+
if (!actionMeta && normalizedAction !== action) {
|
|
3824
|
+
actionMeta = this.resolvedMetadata.actions?.find((candidate) => candidate.action === action);
|
|
3825
|
+
}
|
|
3599
3826
|
if (!actionMeta) {
|
|
3600
|
-
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => candidate.action ===
|
|
3827
|
+
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3601
3828
|
if (ctxAction) {
|
|
3602
3829
|
actionMeta = {
|
|
3603
|
-
action: ctxAction.action,
|
|
3830
|
+
action: this.normalizeCrudActionName(ctxAction.action),
|
|
3604
3831
|
openMode: ctxAction.openMode,
|
|
3605
3832
|
formId: ctxAction.formId,
|
|
3606
3833
|
route: ctxAction.route,
|
|
3607
3834
|
};
|
|
3608
3835
|
}
|
|
3609
3836
|
else {
|
|
3610
|
-
actionMeta = { action };
|
|
3837
|
+
actionMeta = { action: normalizedAction };
|
|
3611
3838
|
}
|
|
3612
3839
|
}
|
|
3613
|
-
const effectiveAction =
|
|
3840
|
+
const effectiveAction = {
|
|
3841
|
+
...(actionMeta || { action: normalizedAction }),
|
|
3842
|
+
action: this.normalizeCrudActionName(actionMeta?.action ?? normalizedAction),
|
|
3843
|
+
};
|
|
3844
|
+
const resourceIdentity = runtimeEvent?.resourceIdentity ?? null;
|
|
3845
|
+
const handledByDuplicateDraft = await this.tryHandleCanonicalDuplicateDraftAction(effectiveAction, contextualRow);
|
|
3846
|
+
if (handledByDuplicateDraft) {
|
|
3847
|
+
return;
|
|
3848
|
+
}
|
|
3614
3849
|
if (!this.hasExplicitOpenBinding(effectiveAction)) {
|
|
3615
|
-
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action,
|
|
3850
|
+
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3616
3851
|
if (openedByDiscovery) {
|
|
3617
3852
|
return;
|
|
3618
3853
|
}
|
|
3619
|
-
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action,
|
|
3854
|
+
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3620
3855
|
if (handledByWorkflowAction) {
|
|
3621
3856
|
return;
|
|
3622
3857
|
}
|
|
3623
3858
|
}
|
|
3624
|
-
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction,
|
|
3859
|
+
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, contextualRow);
|
|
3625
3860
|
if (handledByDelete) {
|
|
3626
3861
|
return;
|
|
3627
3862
|
}
|
|
@@ -3632,7 +3867,7 @@ class PraxisCrudComponent {
|
|
|
3632
3867
|
drawerCloseEmitted = true;
|
|
3633
3868
|
this.afterClose.emit();
|
|
3634
3869
|
};
|
|
3635
|
-
const { mode, ref } = await this.launcher.launch(effectiveAction,
|
|
3870
|
+
const { mode, ref } = await this.launcher.launch(effectiveAction, contextualRow, this.resolvedMetadata, this.componentKeyId(), {
|
|
3636
3871
|
onClose: () => emitDrawerClose(),
|
|
3637
3872
|
onResult: (result) => {
|
|
3638
3873
|
emitDrawerClose();
|
|
@@ -3653,9 +3888,13 @@ class PraxisCrudComponent {
|
|
|
3653
3888
|
capabilities: effectiveAction.action === 'create'
|
|
3654
3889
|
? this.collectionCapabilities
|
|
3655
3890
|
: this.collectionCapabilities,
|
|
3656
|
-
links: this.resolveCrudRuntimeLinks(effectiveAction.action,
|
|
3891
|
+
links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
|
|
3892
|
+
resourceIdentity,
|
|
3657
3893
|
});
|
|
3658
3894
|
this.afterOpen.emit({ mode, action: effectiveAction.action });
|
|
3895
|
+
if (mode === 'drawer') {
|
|
3896
|
+
return;
|
|
3897
|
+
}
|
|
3659
3898
|
if (!ref) {
|
|
3660
3899
|
return;
|
|
3661
3900
|
}
|
|
@@ -3791,6 +4030,9 @@ class PraxisCrudComponent {
|
|
|
3791
4030
|
return this.tx('crud.emptyState.primaryAction', 'Configure metadata');
|
|
3792
4031
|
}
|
|
3793
4032
|
onConfigureRequested() {
|
|
4033
|
+
if (!this.isCustomizationAvailable()) {
|
|
4034
|
+
return;
|
|
4035
|
+
}
|
|
3794
4036
|
this.configureRequested.emit();
|
|
3795
4037
|
this.openCrudAuthoringFromTable();
|
|
3796
4038
|
}
|
|
@@ -3841,9 +4083,140 @@ class PraxisCrudComponent {
|
|
|
3841
4083
|
this.refreshTable();
|
|
3842
4084
|
return true;
|
|
3843
4085
|
}
|
|
4086
|
+
async tryHandleCanonicalDuplicateDraftAction(action, row) {
|
|
4087
|
+
const normalizedAction = this.normalizeCrudActionName(action.action);
|
|
4088
|
+
if (normalizedAction !== 'duplicate-draft' || !row) {
|
|
4089
|
+
return false;
|
|
4090
|
+
}
|
|
4091
|
+
const duplicateDraftUrl = this.getResourceDiscovery().resolveLinkHref(row['_links'], 'duplicate-draft', this.buildDiscoveryOptions());
|
|
4092
|
+
if (!duplicateDraftUrl) {
|
|
4093
|
+
return false;
|
|
4094
|
+
}
|
|
4095
|
+
const response = await firstValueFrom(this.http.post(duplicateDraftUrl, {}));
|
|
4096
|
+
const draft = this.unwrapRestData(response);
|
|
4097
|
+
if (!this.isRecord(draft)) {
|
|
4098
|
+
throw new Error('Duplicate draft action returned an invalid create draft.');
|
|
4099
|
+
}
|
|
4100
|
+
const configuredCreateAction = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === 'create') ?? { action: 'create' };
|
|
4101
|
+
const createAction = {
|
|
4102
|
+
...configuredCreateAction,
|
|
4103
|
+
action: 'create',
|
|
4104
|
+
openMode: configuredCreateAction.openMode ??
|
|
4105
|
+
action.openMode ??
|
|
4106
|
+
this.resolvedMetadata.defaults?.openMode ??
|
|
4107
|
+
'drawer',
|
|
4108
|
+
form: {
|
|
4109
|
+
...(configuredCreateAction.form ?? {}),
|
|
4110
|
+
initialValue: { ...draft },
|
|
4111
|
+
},
|
|
4112
|
+
};
|
|
4113
|
+
let drawerCloseEmitted = false;
|
|
4114
|
+
const emitDrawerClose = () => {
|
|
4115
|
+
if (drawerCloseEmitted)
|
|
4116
|
+
return;
|
|
4117
|
+
drawerCloseEmitted = true;
|
|
4118
|
+
this.afterClose.emit();
|
|
4119
|
+
};
|
|
4120
|
+
const handleResult = (result) => {
|
|
4121
|
+
if (result?.type !== 'save') {
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
const data = this.isRecord(result.data) ? result.data : {};
|
|
4125
|
+
const id = data[this.getIdField()];
|
|
4126
|
+
this.afterSave.emit({ id, data });
|
|
4127
|
+
this.refreshTable();
|
|
4128
|
+
};
|
|
4129
|
+
const { mode, ref } = await this.launcher.launch(createAction, undefined, this.resolvedMetadata, this.componentKeyId(), {
|
|
4130
|
+
onClose: () => emitDrawerClose(),
|
|
4131
|
+
onResult: (result) => handleResult(result),
|
|
4132
|
+
}, {
|
|
4133
|
+
capabilities: this.collectionCapabilities,
|
|
4134
|
+
links: this.tableCollectionLinks,
|
|
4135
|
+
});
|
|
4136
|
+
this.afterOpen.emit({ mode, action: normalizedAction });
|
|
4137
|
+
if (mode !== 'drawer' && ref) {
|
|
4138
|
+
ref
|
|
4139
|
+
.afterClosed()
|
|
4140
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4141
|
+
.subscribe((result) => {
|
|
4142
|
+
this.afterClose.emit();
|
|
4143
|
+
handleResult(result);
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
return true;
|
|
4147
|
+
}
|
|
3844
4148
|
refreshTable() {
|
|
4149
|
+
if (this.tryRefreshMaterializedLocalData()) {
|
|
4150
|
+
return;
|
|
4151
|
+
}
|
|
3845
4152
|
this.table.refetch();
|
|
3846
4153
|
}
|
|
4154
|
+
tryRefreshMaterializedLocalData() {
|
|
4155
|
+
const readUrl = this.resolveMaterializedReadUrl();
|
|
4156
|
+
if (!readUrl || this.resolveResourcePath(this.resolvedMetadata)) {
|
|
4157
|
+
return false;
|
|
4158
|
+
}
|
|
4159
|
+
const refreshSequence = ++this.materializedRefreshSequence;
|
|
4160
|
+
void firstValueFrom(this.getResourceDiscovery().fetchJson(readUrl, {
|
|
4161
|
+
endpointKey: this.resolvedMetadata.resource?.endpointKey,
|
|
4162
|
+
apiUrlEntry: this.resolvedMetadata.resource?.apiUrlEntry,
|
|
4163
|
+
}))
|
|
4164
|
+
.then((response) => {
|
|
4165
|
+
if (!this.isCurrentMaterializedRefresh(refreshSequence, readUrl)) {
|
|
4166
|
+
return;
|
|
4167
|
+
}
|
|
4168
|
+
const data = this.extractCollectionData(this.unwrapRestData(response));
|
|
4169
|
+
if (!data) {
|
|
4170
|
+
this.error.emit({
|
|
4171
|
+
code: 'CRUD_MATERIALIZED_REFRESH_INVALID_COLLECTION',
|
|
4172
|
+
readUrl,
|
|
4173
|
+
});
|
|
4174
|
+
return;
|
|
4175
|
+
}
|
|
4176
|
+
this.resolvedMetadata = {
|
|
4177
|
+
...this.resolvedMetadata,
|
|
4178
|
+
data,
|
|
4179
|
+
};
|
|
4180
|
+
this.applyResolvedCrudState(this.resolvedMetadata);
|
|
4181
|
+
this.cdr.markForCheck();
|
|
4182
|
+
})
|
|
4183
|
+
.catch((error) => this.error.emit(error));
|
|
4184
|
+
return true;
|
|
4185
|
+
}
|
|
4186
|
+
isCurrentMaterializedRefresh(refreshSequence, readUrl) {
|
|
4187
|
+
return (refreshSequence === this.materializedRefreshSequence &&
|
|
4188
|
+
this.resolveMaterializedReadUrl() === readUrl &&
|
|
4189
|
+
this.resolveResourcePath(this.resolvedMetadata).length === 0);
|
|
4190
|
+
}
|
|
4191
|
+
resolveMaterializedReadUrl() {
|
|
4192
|
+
const materialization = this.context?.['materialization'];
|
|
4193
|
+
return String(materialization?.['readUrl'] || '').trim();
|
|
4194
|
+
}
|
|
4195
|
+
unwrapRestData(value) {
|
|
4196
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && 'data' in value) {
|
|
4197
|
+
return value.data;
|
|
4198
|
+
}
|
|
4199
|
+
return value;
|
|
4200
|
+
}
|
|
4201
|
+
extractCollectionData(data) {
|
|
4202
|
+
if (Array.isArray(data)) {
|
|
4203
|
+
return data;
|
|
4204
|
+
}
|
|
4205
|
+
if (!data || typeof data !== 'object') {
|
|
4206
|
+
return null;
|
|
4207
|
+
}
|
|
4208
|
+
const record = data;
|
|
4209
|
+
if (Array.isArray(record['content'])) {
|
|
4210
|
+
return record['content'];
|
|
4211
|
+
}
|
|
4212
|
+
if (Array.isArray(record['items'])) {
|
|
4213
|
+
return record['items'];
|
|
4214
|
+
}
|
|
4215
|
+
if (Array.isArray(record['data'])) {
|
|
4216
|
+
return record['data'];
|
|
4217
|
+
}
|
|
4218
|
+
return null;
|
|
4219
|
+
}
|
|
3847
4220
|
emitTableRuntimeConfigSnapshot() {
|
|
3848
4221
|
const snapshot = this.getCurrentTableConfigSnapshot();
|
|
3849
4222
|
if (!snapshot)
|
|
@@ -3901,17 +4274,67 @@ class PraxisCrudComponent {
|
|
|
3901
4274
|
}
|
|
3902
4275
|
}
|
|
3903
4276
|
resolveQueryContext(meta) {
|
|
4277
|
+
if (Array.isArray(meta?.data) && this.resolveResourcePath(meta).length === 0) {
|
|
4278
|
+
return null;
|
|
4279
|
+
}
|
|
3904
4280
|
return this.isRecord(meta?.queryContext) ? meta.queryContext : null;
|
|
3905
4281
|
}
|
|
3906
4282
|
resolveFilterCriteria(meta) {
|
|
3907
4283
|
return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
|
|
3908
4284
|
}
|
|
4285
|
+
extractRowFromEvent(event) {
|
|
4286
|
+
if (!this.isRecord(event)) {
|
|
4287
|
+
return null;
|
|
4288
|
+
}
|
|
4289
|
+
const row = event['row'];
|
|
4290
|
+
return this.isRecord(row) ? row : null;
|
|
4291
|
+
}
|
|
4292
|
+
extractSelectedRowFromEvent(event) {
|
|
4293
|
+
if (!this.isRecord(event)) {
|
|
4294
|
+
return null;
|
|
4295
|
+
}
|
|
4296
|
+
const selection = event['selection'];
|
|
4297
|
+
if (Array.isArray(selection)) {
|
|
4298
|
+
const first = selection[0];
|
|
4299
|
+
return this.isRecord(first) ? first : null;
|
|
4300
|
+
}
|
|
4301
|
+
const row = event['row'];
|
|
4302
|
+
if (this.isRecord(row)) {
|
|
4303
|
+
return row;
|
|
4304
|
+
}
|
|
4305
|
+
return null;
|
|
4306
|
+
}
|
|
4307
|
+
extractSelectedRowFromBulkAction(event) {
|
|
4308
|
+
const first = Array.isArray(event?.rows) ? event.rows[0] : null;
|
|
4309
|
+
return this.isRecord(first) ? first : null;
|
|
4310
|
+
}
|
|
4311
|
+
extractSelectedRowFromToolbarAction(event) {
|
|
4312
|
+
if (this.isRecord(event?.selectedRow))
|
|
4313
|
+
return event.selectedRow;
|
|
4314
|
+
if (this.isRecord(event?.row))
|
|
4315
|
+
return event.row;
|
|
4316
|
+
const first = Array.isArray(event?.selectedRows) ? event.selectedRows[0] : null;
|
|
4317
|
+
return this.isRecord(first) ? first : null;
|
|
4318
|
+
}
|
|
4319
|
+
extractDiscoveryActionConfig(event) {
|
|
4320
|
+
const config = event?.actionConfig;
|
|
4321
|
+
return this.isRecord(config) && typeof config['resourceKey'] === 'string'
|
|
4322
|
+
? config
|
|
4323
|
+
: null;
|
|
4324
|
+
}
|
|
4325
|
+
resolveSelectedRowForAction(action) {
|
|
4326
|
+
const normalized = String(action || '').trim().toLowerCase();
|
|
4327
|
+
if (!normalized || normalized === 'create' || normalized === 'add' || normalized === 'new') {
|
|
4328
|
+
return undefined;
|
|
4329
|
+
}
|
|
4330
|
+
return this.selectedRow ?? undefined;
|
|
4331
|
+
}
|
|
3909
4332
|
async tryOpenDiscoveredCrudSurface(action, row, runtimeEvent) {
|
|
3910
4333
|
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
3911
4334
|
if (!this.surfaceService) {
|
|
3912
4335
|
return false;
|
|
3913
4336
|
}
|
|
3914
|
-
const providedSurface = this.resolveProvidedSurface(normalizedAction, runtimeEvent
|
|
4337
|
+
const providedSurface = this.resolveProvidedSurface(normalizedAction, this.extractDiscoveryActionConfig(runtimeEvent));
|
|
3915
4338
|
if (!providedSurface && !this.isDiscoveryManagedCrudAction(normalizedAction)) {
|
|
3916
4339
|
return false;
|
|
3917
4340
|
}
|
|
@@ -3942,7 +4365,7 @@ class PraxisCrudComponent {
|
|
|
3942
4365
|
}
|
|
3943
4366
|
let openPromise;
|
|
3944
4367
|
try {
|
|
3945
|
-
|
|
4368
|
+
const surfaceContext = {
|
|
3946
4369
|
sourceId: this.componentKeyId() || undefined,
|
|
3947
4370
|
payload: { action: normalizedAction, row: row ?? null },
|
|
3948
4371
|
meta: {
|
|
@@ -3959,7 +4382,9 @@ class PraxisCrudComponent {
|
|
|
3959
4382
|
resourcePath,
|
|
3960
4383
|
},
|
|
3961
4384
|
},
|
|
3962
|
-
}
|
|
4385
|
+
};
|
|
4386
|
+
const handledInline = await this.surfaceOutlets.tryActivate(payload, surfaceContext);
|
|
4387
|
+
openPromise = handledInline ? Promise.resolve(undefined) : Promise.resolve(this.surfaceService.open(payload, surfaceContext));
|
|
3963
4388
|
}
|
|
3964
4389
|
catch {
|
|
3965
4390
|
return false;
|
|
@@ -3996,10 +4421,10 @@ class PraxisCrudComponent {
|
|
|
3996
4421
|
if (!normalizedAction || this.isDiscoveryManagedCrudAction(normalizedAction) || !this.surfaceService) {
|
|
3997
4422
|
return false;
|
|
3998
4423
|
}
|
|
3999
|
-
const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, runtimeEvent
|
|
4424
|
+
const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, this.extractDiscoveryActionConfig(runtimeEvent));
|
|
4000
4425
|
const catalog = providedAction ? null : await this.resolveDiscoveredActionCatalog(row);
|
|
4001
4426
|
const discoveredAction = providedAction || this.selectDiscoveredWorkflowAction(normalizedAction, catalog?.actions || []);
|
|
4002
|
-
const resourcePath = String(this.resolveResourcePath(this.resolvedMetadata) ||
|
|
4427
|
+
const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
|
|
4003
4428
|
if (!discoveredAction || !resourcePath) {
|
|
4004
4429
|
return false;
|
|
4005
4430
|
}
|
|
@@ -4083,24 +4508,26 @@ class PraxisCrudComponent {
|
|
|
4083
4508
|
if (!candidate || typeof candidate !== 'object') {
|
|
4084
4509
|
return null;
|
|
4085
4510
|
}
|
|
4086
|
-
const
|
|
4511
|
+
const catalogCandidate = candidate;
|
|
4512
|
+
const normalizedId = String(catalogCandidate.id || '').trim().toLowerCase();
|
|
4087
4513
|
if (!normalizedId || normalizedId !== action) {
|
|
4088
4514
|
return null;
|
|
4089
4515
|
}
|
|
4090
|
-
if (!
|
|
4516
|
+
if (!catalogCandidate.operationId || !catalogCandidate.path || !catalogCandidate.method) {
|
|
4091
4517
|
return null;
|
|
4092
4518
|
}
|
|
4093
|
-
return
|
|
4519
|
+
return catalogCandidate;
|
|
4094
4520
|
}
|
|
4095
4521
|
resolveProvidedSurface(action, candidate) {
|
|
4096
4522
|
if (!candidate || typeof candidate !== 'object') {
|
|
4097
4523
|
return null;
|
|
4098
4524
|
}
|
|
4099
|
-
const
|
|
4525
|
+
const catalogCandidate = candidate;
|
|
4526
|
+
const normalizedId = String(catalogCandidate.id || '').trim().toLowerCase();
|
|
4100
4527
|
if (!normalizedId || normalizedId !== action) {
|
|
4101
4528
|
return null;
|
|
4102
4529
|
}
|
|
4103
|
-
const surface =
|
|
4530
|
+
const surface = catalogCandidate;
|
|
4104
4531
|
if (!surface.kind || !surface.scope || !surface.path || !surface.method) {
|
|
4105
4532
|
return null;
|
|
4106
4533
|
}
|
|
@@ -4222,12 +4649,19 @@ class PraxisCrudComponent {
|
|
|
4222
4649
|
}
|
|
4223
4650
|
return cfg.toolbar;
|
|
4224
4651
|
};
|
|
4225
|
-
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.
|
|
4652
|
+
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.shouldCanonicalizeToolbarCreateAction(action));
|
|
4226
4653
|
const addAction = this.resolveCreateToolbarAction(meta, capabilities);
|
|
4227
4654
|
if (addAction) {
|
|
4228
4655
|
if (hasToolbarAdd) {
|
|
4229
4656
|
const toolbar = ensureToolbar();
|
|
4230
4657
|
toolbar.visible = true;
|
|
4658
|
+
toolbar.actions = (toolbar.actions || []).map((action) => this.shouldCanonicalizeToolbarCreateAction(action)
|
|
4659
|
+
? {
|
|
4660
|
+
...action,
|
|
4661
|
+
action: 'create',
|
|
4662
|
+
}
|
|
4663
|
+
: action);
|
|
4664
|
+
changed = true;
|
|
4231
4665
|
}
|
|
4232
4666
|
else {
|
|
4233
4667
|
const toolbar = ensureToolbar();
|
|
@@ -4246,6 +4680,7 @@ class PraxisCrudComponent {
|
|
|
4246
4680
|
});
|
|
4247
4681
|
}
|
|
4248
4682
|
changed = true;
|
|
4683
|
+
changed = this.ensureCanonicalCollectionEmptyState(cfg, meta, addAction) || changed;
|
|
4249
4684
|
}
|
|
4250
4685
|
const discoveredToolbarActions = this.resolveCollectionWorkflowToolbarActions(capabilities);
|
|
4251
4686
|
if (discoveredToolbarActions.length) {
|
|
@@ -4362,6 +4797,28 @@ class PraxisCrudComponent {
|
|
|
4362
4797
|
}
|
|
4363
4798
|
return false;
|
|
4364
4799
|
}
|
|
4800
|
+
normalizeCrudActionName(action) {
|
|
4801
|
+
const raw = String(action || '').trim();
|
|
4802
|
+
const normalized = raw.toLowerCase();
|
|
4803
|
+
return this.isCreateActionAliasName(normalized) ? 'create' : raw;
|
|
4804
|
+
}
|
|
4805
|
+
shouldCanonicalizeToolbarCreateAction(action) {
|
|
4806
|
+
if (!action)
|
|
4807
|
+
return false;
|
|
4808
|
+
const normalize = (value) => String(value || '').trim().toLowerCase();
|
|
4809
|
+
const explicitId = normalize(action.action || action.id || action.code || action.key || action.name || action.type);
|
|
4810
|
+
if (this.isCreateActionAliasName(explicitId))
|
|
4811
|
+
return true;
|
|
4812
|
+
if (explicitId)
|
|
4813
|
+
return false;
|
|
4814
|
+
const icon = normalize(action.icon);
|
|
4815
|
+
const label = normalize(action.label);
|
|
4816
|
+
return ((icon === 'add' || icon === 'add_circle' || icon === 'add_box') &&
|
|
4817
|
+
(label === 'adicionar' || label === 'novo' || label === 'criar' || label === 'incluir'));
|
|
4818
|
+
}
|
|
4819
|
+
isCreateActionAliasName(actionName) {
|
|
4820
|
+
return ['create', 'add', 'novo', 'new', 'incluir', 'inserir'].includes(actionName);
|
|
4821
|
+
}
|
|
4365
4822
|
buildTableCrudContext(meta, capabilities) {
|
|
4366
4823
|
if (!meta)
|
|
4367
4824
|
return undefined;
|
|
@@ -4384,6 +4841,9 @@ class PraxisCrudComponent {
|
|
|
4384
4841
|
};
|
|
4385
4842
|
}
|
|
4386
4843
|
openCrudAuthoringFromTable = () => {
|
|
4844
|
+
if (!this.isCustomizationAvailable()) {
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4387
4847
|
const seed = this.currentAuthoringDocument
|
|
4388
4848
|
|| createCrudAuthoringDocument({ metadata: this.resolvedMetadata });
|
|
4389
4849
|
const ref = openCrudMetadataEditor(this.settingsPanel, {
|
|
@@ -4392,13 +4852,33 @@ class PraxisCrudComponent {
|
|
|
4392
4852
|
title: this.tx('crud.authoring.title', 'Configurações do CRUD'),
|
|
4393
4853
|
titleIcon: 'table_chart',
|
|
4394
4854
|
});
|
|
4855
|
+
this.activeAuthoringPanelRef = ref;
|
|
4395
4856
|
ref.applied$
|
|
4396
4857
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4397
4858
|
.subscribe((payload) => this.applyCrudAuthoringPayload(payload, 'applied'));
|
|
4398
4859
|
ref.saved$
|
|
4399
4860
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4400
4861
|
.subscribe((payload) => this.applyCrudAuthoringPayload(payload, 'saved'));
|
|
4862
|
+
ref.closed$
|
|
4863
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4864
|
+
.subscribe(() => {
|
|
4865
|
+
if (this.activeAuthoringPanelRef === ref) {
|
|
4866
|
+
this.activeAuthoringPanelRef = undefined;
|
|
4867
|
+
}
|
|
4868
|
+
});
|
|
4401
4869
|
};
|
|
4870
|
+
isCustomizationAvailable() {
|
|
4871
|
+
if (!this.enableCustomization) {
|
|
4872
|
+
return false;
|
|
4873
|
+
}
|
|
4874
|
+
const requiredCapability = String(this.authoringCapability || '').trim();
|
|
4875
|
+
if (!requiredCapability) {
|
|
4876
|
+
return true;
|
|
4877
|
+
}
|
|
4878
|
+
const capabilities = this.enterpriseRuntimeContext?.snapshot?.capabilities;
|
|
4879
|
+
return Array.isArray(capabilities)
|
|
4880
|
+
&& capabilities.some((capability) => capability === requiredCapability);
|
|
4881
|
+
}
|
|
4402
4882
|
applyCrudAuthoringPayload(payload, eventName) {
|
|
4403
4883
|
const next = parseLegacyOrCrudDocument(payload);
|
|
4404
4884
|
this.currentAuthoringDocument = next;
|
|
@@ -4615,6 +5095,48 @@ class PraxisCrudComponent {
|
|
|
4615
5095
|
return action;
|
|
4616
5096
|
}
|
|
4617
5097
|
}
|
|
5098
|
+
ensureCanonicalCollectionEmptyState(config, metadata, createAction) {
|
|
5099
|
+
const behavior = config.behavior || {};
|
|
5100
|
+
if (behavior.emptyState) {
|
|
5101
|
+
return false;
|
|
5102
|
+
}
|
|
5103
|
+
const resourceLabel = String(metadata.resource?.label
|
|
5104
|
+
|| metadata.resource?.title
|
|
5105
|
+
|| metadata.resource?.path
|
|
5106
|
+
|| '').trim();
|
|
5107
|
+
const title = resourceLabel
|
|
5108
|
+
? this.txWithParams('crud.table.emptyState.initial.title', 'Sem registros em {label}', { label: resourceLabel })
|
|
5109
|
+
: this.tx('crud.table.emptyState.initial.titleFallback', 'Nenhum registro disponível.');
|
|
5110
|
+
const actionId = String(createAction.action || createAction.id || 'create').trim() || 'create';
|
|
5111
|
+
const actionLabel = String(createAction.label || this.getCrudActionLabel('create')).trim();
|
|
5112
|
+
const actionIcon = String(createAction.icon || 'add').trim();
|
|
5113
|
+
config.behavior = {
|
|
5114
|
+
...behavior,
|
|
5115
|
+
emptyState: {
|
|
5116
|
+
message: '',
|
|
5117
|
+
contexts: {
|
|
5118
|
+
initial: {
|
|
5119
|
+
title,
|
|
5120
|
+
description: this.tx('crud.table.emptyState.initial.descriptionWithAction', 'Use a ação principal para adicionar o primeiro registro quando houver informações para cadastrar.'),
|
|
5121
|
+
actions: [
|
|
5122
|
+
{
|
|
5123
|
+
label: actionLabel,
|
|
5124
|
+
action: actionId,
|
|
5125
|
+
icon: actionIcon,
|
|
5126
|
+
primary: true,
|
|
5127
|
+
},
|
|
5128
|
+
],
|
|
5129
|
+
},
|
|
5130
|
+
filtered: {
|
|
5131
|
+
title: this.tx('crud.table.emptyState.filtered.title', 'Nenhum resultado encontrado.'),
|
|
5132
|
+
description: this.tx('crud.table.emptyState.filtered.description', 'Revise os filtros ou ajuste o termo de busca.'),
|
|
5133
|
+
actions: [],
|
|
5134
|
+
},
|
|
5135
|
+
},
|
|
5136
|
+
},
|
|
5137
|
+
};
|
|
5138
|
+
return true;
|
|
5139
|
+
}
|
|
4618
5140
|
tx(key, fallback) {
|
|
4619
5141
|
try {
|
|
4620
5142
|
return translateCrudRuntimeText(this.i18n, key, fallback);
|
|
@@ -4623,8 +5145,19 @@ class PraxisCrudComponent {
|
|
|
4623
5145
|
return fallback;
|
|
4624
5146
|
}
|
|
4625
5147
|
}
|
|
5148
|
+
txWithParams(key, fallback, params) {
|
|
5149
|
+
try {
|
|
5150
|
+
return this.interpolateTranslationParams(translateCrudRuntimeText(this.i18n, key, fallback, params), params);
|
|
5151
|
+
}
|
|
5152
|
+
catch {
|
|
5153
|
+
return this.interpolateTranslationParams(fallback, params);
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
interpolateTranslationParams(text, params) {
|
|
5157
|
+
return Object.entries(params).reduce((current, [name, value]) => current.split(`{${name}}`).join(value), text);
|
|
5158
|
+
}
|
|
4626
5159
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4627
|
-
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", context: "context", enableCustomization: "enableCustomization" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", rowClick: "rowClick", selectionChange: "selectionChange", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
|
|
5160
|
+
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", context: "context", enableCustomization: "enableCustomization", authoringCapability: "authoringCapability" }, outputs: { configureRequested: "configureRequested", afterOpen: "afterOpen", afterClose: "afterClose", afterSave: "afterSave", afterDelete: "afterDelete", error: "error", rowClick: "rowClick", selectionChange: "selectionChange", tableRuntimeConfigChange: "tableRuntimeConfigChange", crudAuthoringDocumentApplied: "crudAuthoringDocumentApplied", crudAuthoringDocumentSaved: "crudAuthoringDocumentSaved" }, providers: [
|
|
4628
5161
|
providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
|
|
4629
5162
|
providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
|
|
4630
5163
|
], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
@@ -4638,17 +5171,19 @@ class PraxisCrudComponent {
|
|
|
4638
5171
|
[tableId]="crudId || 'default'"
|
|
4639
5172
|
[crudContext]="tableCrudContext"
|
|
4640
5173
|
[enableCustomization]="enableCustomization"
|
|
5174
|
+
[authoringCapability]="authoringCapability"
|
|
4641
5175
|
(rowClick)="onTableRowClick($event)"
|
|
4642
5176
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4643
5177
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4644
|
-
(toolbarAction)="
|
|
5178
|
+
(toolbarAction)="onToolbarAction($event)"
|
|
5179
|
+
(bulkAction)="onBulkAction($event)"
|
|
4645
5180
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4646
5181
|
(reset)="onResetPreferences()"
|
|
4647
5182
|
(metadataChange)="onTableMetadataChange()"
|
|
4648
5183
|
(loadingStateChange)="onTableLoadingStateChange($event)"
|
|
4649
5184
|
></praxis-table>
|
|
4650
5185
|
} @else {
|
|
4651
|
-
@if (
|
|
5186
|
+
@if (isCustomizationAvailable()) {
|
|
4652
5187
|
<praxis-empty-state-card
|
|
4653
5188
|
icon="table_rows"
|
|
4654
5189
|
[title]="getEmptyStateTitle()"
|
|
@@ -4657,7 +5192,7 @@ class PraxisCrudComponent {
|
|
|
4657
5192
|
/>
|
|
4658
5193
|
}
|
|
4659
5194
|
}
|
|
4660
|
-
`, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\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", "dense"], outputs: ["rowClick", "widgetEvent", "rowDoubleClick", "rowExpansionChange", "rowAction", "toolbarAction", "bulkAction", "exportAction", "columnReorder", "columnReorderAttempt", "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"] }] });
|
|
5195
|
+
`, isInline: true, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\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"] }] });
|
|
4661
5196
|
}
|
|
4662
5197
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
|
|
4663
5198
|
type: Component,
|
|
@@ -4675,17 +5210,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4675
5210
|
[tableId]="crudId || 'default'"
|
|
4676
5211
|
[crudContext]="tableCrudContext"
|
|
4677
5212
|
[enableCustomization]="enableCustomization"
|
|
5213
|
+
[authoringCapability]="authoringCapability"
|
|
4678
5214
|
(rowClick)="onTableRowClick($event)"
|
|
4679
5215
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4680
5216
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4681
|
-
(toolbarAction)="
|
|
5217
|
+
(toolbarAction)="onToolbarAction($event)"
|
|
5218
|
+
(bulkAction)="onBulkAction($event)"
|
|
4682
5219
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4683
5220
|
(reset)="onResetPreferences()"
|
|
4684
5221
|
(metadataChange)="onTableMetadataChange()"
|
|
4685
5222
|
(loadingStateChange)="onTableLoadingStateChange($event)"
|
|
4686
5223
|
></praxis-table>
|
|
4687
5224
|
} @else {
|
|
4688
|
-
@if (
|
|
5225
|
+
@if (isCustomizationAvailable()) {
|
|
4689
5226
|
<praxis-empty-state-card
|
|
4690
5227
|
icon="table_rows"
|
|
4691
5228
|
[title]="getEmptyStateTitle()"
|
|
@@ -4695,7 +5232,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4695
5232
|
}
|
|
4696
5233
|
}
|
|
4697
5234
|
`, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"] }]
|
|
4698
|
-
}], propDecorators: { metadata: [{
|
|
5235
|
+
}], ctorParameters: () => [], propDecorators: { metadata: [{
|
|
4699
5236
|
type: Input,
|
|
4700
5237
|
args: [{ required: true }]
|
|
4701
5238
|
}], crudId: [{
|
|
@@ -4707,6 +5244,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4707
5244
|
type: Input
|
|
4708
5245
|
}], enableCustomization: [{
|
|
4709
5246
|
type: Input
|
|
5247
|
+
}], authoringCapability: [{
|
|
5248
|
+
type: Input
|
|
4710
5249
|
}], configureRequested: [{
|
|
4711
5250
|
type: Output
|
|
4712
5251
|
}], afterOpen: [{
|
|
@@ -4734,6 +5273,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4734
5273
|
args: [PraxisTable]
|
|
4735
5274
|
}] } });
|
|
4736
5275
|
|
|
5276
|
+
function isCrudDebugEnabled() {
|
|
5277
|
+
try {
|
|
5278
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
5279
|
+
return true;
|
|
5280
|
+
}
|
|
5281
|
+
if (typeof localStorage !== 'undefined') {
|
|
5282
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
5285
|
+
catch { }
|
|
5286
|
+
return false;
|
|
5287
|
+
}
|
|
5288
|
+
function debugCrudHost(message, data) {
|
|
5289
|
+
if (!isCrudDebugEnabled()) {
|
|
5290
|
+
return;
|
|
5291
|
+
}
|
|
5292
|
+
try {
|
|
5293
|
+
console.debug(message, data);
|
|
5294
|
+
}
|
|
5295
|
+
catch { }
|
|
5296
|
+
}
|
|
4737
5297
|
class DynamicFormDialogHostComponent {
|
|
4738
5298
|
dialogRef;
|
|
4739
5299
|
data;
|
|
@@ -4742,21 +5302,28 @@ class DynamicFormDialogHostComponent {
|
|
|
4742
5302
|
configStorage;
|
|
4743
5303
|
formComp;
|
|
4744
5304
|
modal = {};
|
|
5305
|
+
presentation = 'modal';
|
|
4745
5306
|
maximized = false;
|
|
4746
5307
|
initialSize = {};
|
|
5308
|
+
initialPosition;
|
|
4747
5309
|
rememberState = false;
|
|
4748
5310
|
stateKey;
|
|
4749
5311
|
backDefaults = {};
|
|
4750
5312
|
destroyRef = inject(DestroyRef);
|
|
5313
|
+
i18n = inject(PraxisI18nService);
|
|
4751
5314
|
resourcePath;
|
|
4752
5315
|
resourceId;
|
|
4753
5316
|
initialValue;
|
|
5317
|
+
resourceIdentity = null;
|
|
4754
5318
|
schemaUrl;
|
|
4755
5319
|
submitUrl;
|
|
4756
5320
|
submitMethod;
|
|
4757
5321
|
apiEndpointKey;
|
|
4758
5322
|
apiUrlEntry;
|
|
5323
|
+
layoutPolicy;
|
|
5324
|
+
formConfig = {};
|
|
4759
5325
|
formActions;
|
|
5326
|
+
formConfigPersistenceStrategy = 'input-first';
|
|
4760
5327
|
mode = 'create';
|
|
4761
5328
|
backConfig;
|
|
4762
5329
|
idField = 'id';
|
|
@@ -4766,6 +5333,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4766
5333
|
closeLabel: 'Fechar diálogo',
|
|
4767
5334
|
maximizeLabel: 'Maximizar',
|
|
4768
5335
|
restoreLabel: 'Restaurar',
|
|
5336
|
+
recordContextLabel: translateCrudRuntimeText(this.i18n, 'crud.dialog.recordContextLabel', 'Registro em edição'),
|
|
4769
5337
|
discardTitle: 'Descartar alterações?',
|
|
4770
5338
|
discardMessage: 'Você tem alterações não salvas. Deseja fechar assim mesmo?',
|
|
4771
5339
|
discardConfirm: 'Descartar',
|
|
@@ -4778,6 +5346,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4778
5346
|
this.crud = crud;
|
|
4779
5347
|
this.configStorage = configStorage;
|
|
4780
5348
|
this.dialogRef.disableClose = true;
|
|
5349
|
+
this.presentation = this.data.presentation === 'drawer' ? 'drawer' : 'modal';
|
|
4781
5350
|
// i18n
|
|
4782
5351
|
this.texts = {
|
|
4783
5352
|
...this.texts,
|
|
@@ -4788,6 +5357,9 @@ class DynamicFormDialogHostComponent {
|
|
|
4788
5357
|
canMaximize: true,
|
|
4789
5358
|
...(this.data.metadata?.defaults?.modal || {}),
|
|
4790
5359
|
};
|
|
5360
|
+
this.initialPosition = this.presentation === 'drawer'
|
|
5361
|
+
? (this.data.dialogPosition ?? this.modal.position ?? { right: '0', top: '0' })
|
|
5362
|
+
: undefined;
|
|
4791
5363
|
this.rememberState = !!this.modal.rememberLastState;
|
|
4792
5364
|
this.stateKey = this.data.action?.formId
|
|
4793
5365
|
? `crud-dialog-state:${this.data.action.formId}`
|
|
@@ -4812,12 +5384,17 @@ class DynamicFormDialogHostComponent {
|
|
|
4812
5384
|
this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
|
|
4813
5385
|
const act = this.data.action?.action;
|
|
4814
5386
|
this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
|
|
5387
|
+
this.resourceIdentity = this.mode === 'edit' && this.data.resourceIdentity
|
|
5388
|
+
? this.data.resourceIdentity
|
|
5389
|
+
: null;
|
|
5390
|
+
this.formConfig = this.resolveFormConfig();
|
|
5391
|
+
this.layoutPolicy = this.resolveLayoutPolicy();
|
|
4815
5392
|
this.formActions = this.resolveFormActions();
|
|
4816
5393
|
// Back config: defaults from metadata/action, overridden by saved per-form config
|
|
4817
5394
|
const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
|
|
4818
5395
|
this.backDefaults = defaults;
|
|
4819
5396
|
this.backConfig = { ...defaults };
|
|
4820
|
-
|
|
5397
|
+
debugCrudHost('[CRUD:Host] constructed', {
|
|
4821
5398
|
action: this.data?.action,
|
|
4822
5399
|
resourcePath: this.resourcePath,
|
|
4823
5400
|
resourceId: this.resourceId,
|
|
@@ -4857,6 +5434,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4857
5434
|
'apiEndpointKey',
|
|
4858
5435
|
'apiUrlEntry',
|
|
4859
5436
|
'initialValue',
|
|
5437
|
+
'layoutPolicy',
|
|
4860
5438
|
]);
|
|
4861
5439
|
const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
|
|
4862
5440
|
? { ...inputs['initialValue'] }
|
|
@@ -4869,10 +5447,50 @@ class DynamicFormDialogHostComponent {
|
|
|
4869
5447
|
}
|
|
4870
5448
|
return Object.keys(explicit).length ? explicit : null;
|
|
4871
5449
|
}
|
|
5450
|
+
resolveFormConfig() {
|
|
5451
|
+
const config = this.data.metadata?.form;
|
|
5452
|
+
return config && typeof config === 'object' ? config : {};
|
|
5453
|
+
}
|
|
5454
|
+
get dialogTitle() {
|
|
5455
|
+
const actionLabel = this.resolveActionDialogTitle();
|
|
5456
|
+
return actionLabel ||
|
|
5457
|
+
stringOrUndefined(this.formConfig['title']) ||
|
|
5458
|
+
deriveModeTitle(this.mode, this.resolveResourceTitle()) ||
|
|
5459
|
+
this.texts.title;
|
|
5460
|
+
}
|
|
5461
|
+
resolveActionDialogTitle() {
|
|
5462
|
+
const actionLabel = stringOrUndefined(this.data.action?.label);
|
|
5463
|
+
if (!actionLabel) {
|
|
5464
|
+
return undefined;
|
|
5465
|
+
}
|
|
5466
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5467
|
+
const table = this.data.metadata?.table ?? {};
|
|
5468
|
+
const businessTitle = stringOrUndefined(resource.formTitle ??
|
|
5469
|
+
resource.title ??
|
|
5470
|
+
resource.label ??
|
|
5471
|
+
table.title ??
|
|
5472
|
+
table.label);
|
|
5473
|
+
const routeFallbackTitle = deriveModeTitle(this.mode, titleFromResourcePath(this.resourcePath));
|
|
5474
|
+
return businessTitle && routeFallbackTitle && sameTitle(actionLabel, routeFallbackTitle)
|
|
5475
|
+
? undefined
|
|
5476
|
+
: actionLabel;
|
|
5477
|
+
}
|
|
5478
|
+
resolveResourceTitle() {
|
|
5479
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5480
|
+
const table = this.data.metadata?.table ?? {};
|
|
5481
|
+
return stringOrUndefined(resource.formTitle ??
|
|
5482
|
+
resource.title ??
|
|
5483
|
+
resource.label ??
|
|
5484
|
+
table.title ??
|
|
5485
|
+
table.label) || titleFromResourcePath(this.resourcePath);
|
|
5486
|
+
}
|
|
4872
5487
|
resolveFormActions() {
|
|
4873
5488
|
if (this.mode === 'view') {
|
|
4874
5489
|
return undefined;
|
|
4875
5490
|
}
|
|
5491
|
+
if (this.formConfig?.actions) {
|
|
5492
|
+
return undefined;
|
|
5493
|
+
}
|
|
4876
5494
|
const submitLabel = this.resolveSubmitLabel();
|
|
4877
5495
|
if (!submitLabel) {
|
|
4878
5496
|
return undefined;
|
|
@@ -4890,6 +5508,15 @@ class DynamicFormDialogHostComponent {
|
|
|
4890
5508
|
},
|
|
4891
5509
|
};
|
|
4892
5510
|
}
|
|
5511
|
+
resolveLayoutPolicy() {
|
|
5512
|
+
const explicit = this.data.inputs?.['layoutPolicy'] ??
|
|
5513
|
+
this.data.action?.form?.layoutPolicy ??
|
|
5514
|
+
this.data.action?.layoutPolicy;
|
|
5515
|
+
if (explicit && typeof explicit === 'object') {
|
|
5516
|
+
return explicit;
|
|
5517
|
+
}
|
|
5518
|
+
return null;
|
|
5519
|
+
}
|
|
4893
5520
|
resolveSubmitLabel() {
|
|
4894
5521
|
const action = this.data.action ?? {};
|
|
4895
5522
|
const explicit = stringOrUndefined(action.form?.submitLabel ??
|
|
@@ -4962,13 +5589,17 @@ class DynamicFormDialogHostComponent {
|
|
|
4962
5589
|
ref
|
|
4963
5590
|
.afterClosed()
|
|
4964
5591
|
.pipe(filter((confirmed) => !!confirmed), takeUntilDestroyed(this.destroyRef))
|
|
4965
|
-
.subscribe(() => this.dialogRef.close());
|
|
5592
|
+
.subscribe(() => this.dialogRef.close({ type: 'close' }));
|
|
4966
5593
|
}
|
|
4967
5594
|
else {
|
|
4968
|
-
this.dialogRef.close();
|
|
5595
|
+
this.dialogRef.close({ type: 'close' });
|
|
4969
5596
|
}
|
|
4970
5597
|
}
|
|
4971
5598
|
toggleMaximize(initial = false) {
|
|
5599
|
+
const pane = this.resolveOverlayPane();
|
|
5600
|
+
if (!initial && !this.maximized && pane) {
|
|
5601
|
+
this.captureRenderedInitialSize(pane);
|
|
5602
|
+
}
|
|
4972
5603
|
this.maximized = initial ? true : !this.maximized;
|
|
4973
5604
|
const gap = this.maximized ? this.modal.edgeGap ?? 8 : undefined;
|
|
4974
5605
|
const width = this.maximized
|
|
@@ -4978,19 +5609,40 @@ class DynamicFormDialogHostComponent {
|
|
|
4978
5609
|
? `calc(100dvh - ${2 * (gap ?? 0)}px)`
|
|
4979
5610
|
: this.initialSize.height;
|
|
4980
5611
|
this.dialogRef.updateSize(width, height);
|
|
4981
|
-
this.
|
|
4982
|
-
const pane = this.dialogRef
|
|
4983
|
-
?._containerInstance?._elementRef?.nativeElement?.parentElement;
|
|
5612
|
+
this.updateHostPosition();
|
|
4984
5613
|
if (pane && pane.classList.contains('pfx-dialog-pane')) {
|
|
4985
|
-
|
|
5614
|
+
if (this.presentation === 'drawer') {
|
|
5615
|
+
pane.classList.toggle('pfx-drawer-maximized', this.maximized);
|
|
5616
|
+
if (this.maximized) {
|
|
5617
|
+
pane.style.setProperty('--pfx-drawer-edge-gap', `${gap ?? 0}px`);
|
|
5618
|
+
}
|
|
5619
|
+
else {
|
|
5620
|
+
pane.style.removeProperty('--pfx-drawer-edge-gap');
|
|
5621
|
+
}
|
|
5622
|
+
}
|
|
5623
|
+
else {
|
|
5624
|
+
pane.style.margin = this.maximized ? `${gap}px` : '';
|
|
5625
|
+
}
|
|
4986
5626
|
}
|
|
4987
5627
|
this.saveState();
|
|
4988
5628
|
}
|
|
5629
|
+
resolveOverlayPane() {
|
|
5630
|
+
return this.dialogRef
|
|
5631
|
+
?._containerInstance?._elementRef?.nativeElement?.parentElement;
|
|
5632
|
+
}
|
|
5633
|
+
captureRenderedInitialSize(pane) {
|
|
5634
|
+
const style = getComputedStyle(pane);
|
|
5635
|
+
const renderedWidth = Number.parseFloat(style.width) > 0 ? style.width : undefined;
|
|
5636
|
+
const renderedHeight = Number.parseFloat(style.height) > 0 ? style.height : undefined;
|
|
5637
|
+
this.initialSize = {
|
|
5638
|
+
width: this.initialSize.width ?? renderedWidth,
|
|
5639
|
+
height: this.initialSize.height ?? renderedHeight,
|
|
5640
|
+
};
|
|
5641
|
+
}
|
|
4989
5642
|
saveState() {
|
|
4990
5643
|
if (!this.rememberState || !this.stateKey)
|
|
4991
5644
|
return;
|
|
4992
|
-
const pane = this.
|
|
4993
|
-
?._containerInstance?._elementRef?.nativeElement?.parentElement;
|
|
5645
|
+
const pane = this.resolveOverlayPane();
|
|
4994
5646
|
const style = pane ? getComputedStyle(pane) : null;
|
|
4995
5647
|
const currentWidth = style?.width || this.initialSize.width;
|
|
4996
5648
|
const currentHeight = style?.height || this.initialSize.height;
|
|
@@ -5008,7 +5660,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5008
5660
|
width: saved?.width ?? this.modal.width,
|
|
5009
5661
|
height: saved?.height ?? this.modal.height,
|
|
5010
5662
|
};
|
|
5011
|
-
|
|
5663
|
+
debugCrudHost('[CRUD:Host] ngOnInit', {
|
|
5012
5664
|
initialSize: this.initialSize,
|
|
5013
5665
|
startMaximized: this.modal.startMaximized,
|
|
5014
5666
|
fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
|
|
@@ -5028,52 +5680,89 @@ class DynamicFormDialogHostComponent {
|
|
|
5028
5680
|
}
|
|
5029
5681
|
else if (this.initialSize.width || this.initialSize.height) {
|
|
5030
5682
|
this.dialogRef.updateSize(this.initialSize.width, this.initialSize.height);
|
|
5683
|
+
this.updateHostPosition();
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
updateHostPosition() {
|
|
5687
|
+
if (this.presentation !== 'drawer') {
|
|
5031
5688
|
this.dialogRef.updatePosition();
|
|
5689
|
+
return;
|
|
5032
5690
|
}
|
|
5691
|
+
this.dialogRef.updatePosition(this.maximized
|
|
5692
|
+
? this.resolveMaximizedDrawerPosition()
|
|
5693
|
+
: this.initialPosition);
|
|
5694
|
+
}
|
|
5695
|
+
resolveMaximizedDrawerPosition() {
|
|
5696
|
+
const position = this.initialPosition ?? { right: '0', top: '0' };
|
|
5697
|
+
const offset = `${this.modal.edgeGap ?? 8}px`;
|
|
5698
|
+
const horizontal = position.left != null
|
|
5699
|
+
? { left: offset }
|
|
5700
|
+
: { right: offset };
|
|
5701
|
+
const vertical = position.bottom != null && position.top == null
|
|
5702
|
+
? { bottom: offset }
|
|
5703
|
+
: { top: offset };
|
|
5704
|
+
return { ...horizontal, ...vertical };
|
|
5033
5705
|
}
|
|
5034
5706
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, deps: [{ token: MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: DialogService }, { token: i2$1.GenericCrudService }, { token: ASYNC_CONFIG_STORAGE }], target: i0.ɵɵFactoryTarget.Component });
|
|
5035
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicFormDialogHostComponent, isStandalone: true, selector: "praxis-dynamic-form-dialog-host", host: { properties: { "attr.data-density": "modal.density || \"default\""
|
|
5707
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DynamicFormDialogHostComponent, isStandalone: true, selector: "praxis-dynamic-form-dialog-host", host: { properties: { "attr.data-density": "modal.density || \"default\"", "attr.data-presentation": "presentation", "class.praxis-drawer": "presentation === \"drawer\"" }, classAttribute: "praxis-dialog" }, providers: [
|
|
5708
|
+
GenericCrudService,
|
|
5709
|
+
providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
|
|
5710
|
+
], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
|
|
5036
5711
|
<div mat-dialog-title class="dialog-header">
|
|
5037
5712
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5038
|
-
{{
|
|
5713
|
+
{{ dialogTitle }}
|
|
5039
5714
|
</h2>
|
|
5040
5715
|
<span class="spacer"></span>
|
|
5041
5716
|
@if (modal.canMaximize) {
|
|
5042
5717
|
<button
|
|
5043
|
-
|
|
5718
|
+
[praxisIconButton]="maximized ? 'close_fullscreen' : 'open_in_full'"
|
|
5719
|
+
size="compact"
|
|
5044
5720
|
type="button"
|
|
5045
5721
|
(click)="toggleMaximize()"
|
|
5046
5722
|
[attr.aria-label]="maximized ? texts.restoreLabel : texts.maximizeLabel"
|
|
5047
|
-
>
|
|
5048
|
-
<mat-icon [praxisIcon]="maximized ? 'close_fullscreen' : 'open_in_full'"></mat-icon>
|
|
5049
|
-
</button>
|
|
5723
|
+
></button>
|
|
5050
5724
|
}
|
|
5051
5725
|
<button
|
|
5052
|
-
|
|
5726
|
+
praxisIconButton="close"
|
|
5727
|
+
size="compact"
|
|
5053
5728
|
type="button"
|
|
5054
5729
|
(click)="onCancel()"
|
|
5055
5730
|
[attr.aria-label]="texts.closeLabel"
|
|
5056
5731
|
cdkFocusInitial
|
|
5057
|
-
>
|
|
5058
|
-
<mat-icon [praxisIcon]="'close'"></mat-icon>
|
|
5059
|
-
</button>
|
|
5732
|
+
></button>
|
|
5060
5733
|
</div>
|
|
5061
5734
|
|
|
5062
5735
|
<mat-dialog-content
|
|
5063
5736
|
class="dialog-content"
|
|
5064
5737
|
aria-labelledby="crudDialogTitle"
|
|
5065
5738
|
>
|
|
5739
|
+
@if (resourceIdentity) {
|
|
5740
|
+
<div class="crud-resource-identity" data-testid="crud-resource-identity">
|
|
5741
|
+
<span class="crud-resource-identity__label" aria-hidden="true">
|
|
5742
|
+
{{ texts.recordContextLabel }}
|
|
5743
|
+
</span>
|
|
5744
|
+
<praxis-resource-identity
|
|
5745
|
+
[identity]="resourceIdentity"
|
|
5746
|
+
density="compact"
|
|
5747
|
+
[showKeyLabel]="true"
|
|
5748
|
+
[ariaLabel]="texts.recordContextLabel"
|
|
5749
|
+
></praxis-resource-identity>
|
|
5750
|
+
</div>
|
|
5751
|
+
}
|
|
5066
5752
|
<praxis-dynamic-form
|
|
5067
5753
|
[formId]="data.action?.formId"
|
|
5068
5754
|
[resourcePath]="resourcePath"
|
|
5069
5755
|
[resourceId]="resourceId"
|
|
5070
5756
|
[initialValue]="initialValue"
|
|
5071
5757
|
[mode]="mode"
|
|
5758
|
+
[config]="formConfig"
|
|
5072
5759
|
[schemaUrl]="schemaUrl"
|
|
5073
5760
|
[submitUrl]="submitUrl"
|
|
5074
5761
|
[submitMethod]="submitMethod"
|
|
5075
5762
|
[apiEndpointKey]="apiEndpointKey"
|
|
5076
5763
|
[apiUrlEntry]="apiUrlEntry"
|
|
5764
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5765
|
+
[layoutPolicy]="layoutPolicy"
|
|
5077
5766
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5078
5767
|
[backConfig]="backConfig"
|
|
5079
5768
|
[actions]="formActions"
|
|
@@ -5081,61 +5770,79 @@ class DynamicFormDialogHostComponent {
|
|
|
5081
5770
|
(formCancel)="onCancel()"
|
|
5082
5771
|
></praxis-dynamic-form>
|
|
5083
5772
|
</mat-dialog-content>
|
|
5084
|
-
`, isInline: true, styles: ["
|
|
5773
|
+
`, 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);padding:0 var(--dlg-pad);height:var(--dlg-header-h);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 .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-resource-identity{position:sticky;top:0;z-index:1;display:grid;gap:.25rem;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__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: 64px;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{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%;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{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: "component", type: PraxisIconButtonComponent, selector: "button[praxisIconButton]", inputs: ["praxisIconButton", "size", "appearance", "presentation", "pressed", "busy"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels", "showKeyLabel", "ariaLabel"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "initialValue", "editorialContext", "mode", "config", "actions", "schemaSource", "schemaUrl", "readUrl", "submitUrl", "submitMethod", "responseSchemaUrl", "apiEndpointKey", "apiUrlEntry", "enableCustomization", "showAiAssistant", "formId", "componentInstanceId", "configPersistenceStrategy", "layout", "generatedLayoutPreset", "layoutPolicy", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "fieldIconPolicy", "domainRules", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "configPatchChange", "formReady", "valueChange", "syncCompleted", "initializationError", "loadingStateChange", "enableCustomizationChange", "customAction", "actionConfirmation", "schemaStatusChange", "fieldRenderError", "ruleDiagnosticsChange"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
5085
5774
|
}
|
|
5086
5775
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
|
|
5087
5776
|
type: Component,
|
|
5088
5777
|
args: [{ selector: 'praxis-dynamic-form-dialog-host', standalone: true, imports: [
|
|
5089
5778
|
MatDialogModule,
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
PraxisIconDirective,
|
|
5779
|
+
PraxisIconButtonComponent,
|
|
5780
|
+
PraxisResourceIdentityComponent,
|
|
5093
5781
|
PraxisDynamicForm
|
|
5094
|
-
],
|
|
5782
|
+
], encapsulation: ViewEncapsulation.None, providers: [
|
|
5783
|
+
GenericCrudService,
|
|
5784
|
+
providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
|
|
5785
|
+
], host: {
|
|
5095
5786
|
class: 'praxis-dialog',
|
|
5096
5787
|
'[attr.data-density]': 'modal.density || "default"',
|
|
5788
|
+
'[attr.data-presentation]': 'presentation',
|
|
5789
|
+
'[class.praxis-drawer]': 'presentation === "drawer"',
|
|
5097
5790
|
}, template: `
|
|
5098
5791
|
<div mat-dialog-title class="dialog-header">
|
|
5099
5792
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5100
|
-
{{
|
|
5793
|
+
{{ dialogTitle }}
|
|
5101
5794
|
</h2>
|
|
5102
5795
|
<span class="spacer"></span>
|
|
5103
5796
|
@if (modal.canMaximize) {
|
|
5104
5797
|
<button
|
|
5105
|
-
|
|
5798
|
+
[praxisIconButton]="maximized ? 'close_fullscreen' : 'open_in_full'"
|
|
5799
|
+
size="compact"
|
|
5106
5800
|
type="button"
|
|
5107
5801
|
(click)="toggleMaximize()"
|
|
5108
5802
|
[attr.aria-label]="maximized ? texts.restoreLabel : texts.maximizeLabel"
|
|
5109
|
-
>
|
|
5110
|
-
<mat-icon [praxisIcon]="maximized ? 'close_fullscreen' : 'open_in_full'"></mat-icon>
|
|
5111
|
-
</button>
|
|
5803
|
+
></button>
|
|
5112
5804
|
}
|
|
5113
5805
|
<button
|
|
5114
|
-
|
|
5806
|
+
praxisIconButton="close"
|
|
5807
|
+
size="compact"
|
|
5115
5808
|
type="button"
|
|
5116
5809
|
(click)="onCancel()"
|
|
5117
5810
|
[attr.aria-label]="texts.closeLabel"
|
|
5118
5811
|
cdkFocusInitial
|
|
5119
|
-
>
|
|
5120
|
-
<mat-icon [praxisIcon]="'close'"></mat-icon>
|
|
5121
|
-
</button>
|
|
5812
|
+
></button>
|
|
5122
5813
|
</div>
|
|
5123
5814
|
|
|
5124
5815
|
<mat-dialog-content
|
|
5125
5816
|
class="dialog-content"
|
|
5126
5817
|
aria-labelledby="crudDialogTitle"
|
|
5127
5818
|
>
|
|
5819
|
+
@if (resourceIdentity) {
|
|
5820
|
+
<div class="crud-resource-identity" data-testid="crud-resource-identity">
|
|
5821
|
+
<span class="crud-resource-identity__label" aria-hidden="true">
|
|
5822
|
+
{{ texts.recordContextLabel }}
|
|
5823
|
+
</span>
|
|
5824
|
+
<praxis-resource-identity
|
|
5825
|
+
[identity]="resourceIdentity"
|
|
5826
|
+
density="compact"
|
|
5827
|
+
[showKeyLabel]="true"
|
|
5828
|
+
[ariaLabel]="texts.recordContextLabel"
|
|
5829
|
+
></praxis-resource-identity>
|
|
5830
|
+
</div>
|
|
5831
|
+
}
|
|
5128
5832
|
<praxis-dynamic-form
|
|
5129
5833
|
[formId]="data.action?.formId"
|
|
5130
5834
|
[resourcePath]="resourcePath"
|
|
5131
5835
|
[resourceId]="resourceId"
|
|
5132
5836
|
[initialValue]="initialValue"
|
|
5133
5837
|
[mode]="mode"
|
|
5838
|
+
[config]="formConfig"
|
|
5134
5839
|
[schemaUrl]="schemaUrl"
|
|
5135
5840
|
[submitUrl]="submitUrl"
|
|
5136
5841
|
[submitMethod]="submitMethod"
|
|
5137
5842
|
[apiEndpointKey]="apiEndpointKey"
|
|
5138
5843
|
[apiUrlEntry]="apiUrlEntry"
|
|
5844
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5845
|
+
[layoutPolicy]="layoutPolicy"
|
|
5139
5846
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5140
5847
|
[backConfig]="backConfig"
|
|
5141
5848
|
[actions]="formActions"
|
|
@@ -5143,7 +5850,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5143
5850
|
(formCancel)="onCancel()"
|
|
5144
5851
|
></praxis-dynamic-form>
|
|
5145
5852
|
</mat-dialog-content>
|
|
5146
|
-
`, styles: ["
|
|
5853
|
+
`, 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);padding:0 var(--dlg-pad);height:var(--dlg-header-h);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 .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-resource-identity{position:sticky;top:0;z-index:1;display:grid;gap:.25rem;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__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: 64px;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{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%;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{border-radius:0!important}\n"] }]
|
|
5147
5854
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
5148
5855
|
type: Inject,
|
|
5149
5856
|
args: [MatDialogRef]
|
|
@@ -5170,6 +5877,17 @@ function stringOrUndefined(value) {
|
|
|
5170
5877
|
const text = String(value ?? '').trim();
|
|
5171
5878
|
return text || undefined;
|
|
5172
5879
|
}
|
|
5880
|
+
function sameTitle(left, right) {
|
|
5881
|
+
return normalizeTitle(left) === normalizeTitle(right);
|
|
5882
|
+
}
|
|
5883
|
+
function normalizeTitle(value) {
|
|
5884
|
+
return value
|
|
5885
|
+
.normalize('NFD')
|
|
5886
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
5887
|
+
.replace(/\s+/g, ' ')
|
|
5888
|
+
.trim()
|
|
5889
|
+
.toLocaleLowerCase('pt-BR');
|
|
5890
|
+
}
|
|
5173
5891
|
function deriveCreateSubmitLabel(actionLabel) {
|
|
5174
5892
|
if (!actionLabel) {
|
|
5175
5893
|
return 'Criar';
|
|
@@ -5180,6 +5898,61 @@ function deriveCreateSubmitLabel(actionLabel) {
|
|
|
5180
5898
|
}
|
|
5181
5899
|
return actionLabel;
|
|
5182
5900
|
}
|
|
5901
|
+
function deriveModeTitle(mode, resourceTitle) {
|
|
5902
|
+
if (!resourceTitle) {
|
|
5903
|
+
return undefined;
|
|
5904
|
+
}
|
|
5905
|
+
const singular = lowerFirstLetter(singularizePtBrResourceTitle(resourceTitle));
|
|
5906
|
+
if (mode === 'create') {
|
|
5907
|
+
return `Adicionar ${singular}`;
|
|
5908
|
+
}
|
|
5909
|
+
if (mode === 'edit') {
|
|
5910
|
+
return `Editar ${singular}`;
|
|
5911
|
+
}
|
|
5912
|
+
return `Visualizar ${singular}`;
|
|
5913
|
+
}
|
|
5914
|
+
function titleFromResourcePath(resourcePath) {
|
|
5915
|
+
const lastSegment = resourcePath?.split('/').filter(Boolean).pop();
|
|
5916
|
+
if (!lastSegment) {
|
|
5917
|
+
return undefined;
|
|
5918
|
+
}
|
|
5919
|
+
return lastSegment
|
|
5920
|
+
.replace(/[-_]+/g, ' ')
|
|
5921
|
+
.replace(/\s+/g, ' ')
|
|
5922
|
+
.trim()
|
|
5923
|
+
.toLocaleLowerCase('pt-BR');
|
|
5924
|
+
}
|
|
5925
|
+
function singularizePtBrResourceTitle(title) {
|
|
5926
|
+
const normalized = title.trim();
|
|
5927
|
+
const lower = normalized.toLocaleLowerCase('pt-BR');
|
|
5928
|
+
if (lower === 'códigos de frequência' || lower === 'codigos de frequencia') {
|
|
5929
|
+
return 'código de frequência';
|
|
5930
|
+
}
|
|
5931
|
+
const [first, ...rest] = normalized.split(/\s+/);
|
|
5932
|
+
const singularFirst = singularizePtBrWord(first);
|
|
5933
|
+
return [singularFirst, ...rest].join(' ');
|
|
5934
|
+
}
|
|
5935
|
+
function singularizePtBrWord(word) {
|
|
5936
|
+
if (/ões$/i.test(word)) {
|
|
5937
|
+
return word.replace(/ões$/i, 'ão');
|
|
5938
|
+
}
|
|
5939
|
+
if (/ais$/i.test(word)) {
|
|
5940
|
+
return word.replace(/ais$/i, 'al');
|
|
5941
|
+
}
|
|
5942
|
+
if (/eis$/i.test(word)) {
|
|
5943
|
+
return word.replace(/eis$/i, 'el');
|
|
5944
|
+
}
|
|
5945
|
+
if (/res$/i.test(word)) {
|
|
5946
|
+
return word.replace(/es$/i, '');
|
|
5947
|
+
}
|
|
5948
|
+
if (/s$/i.test(word) && !/ss$/i.test(word)) {
|
|
5949
|
+
return word.replace(/s$/i, '');
|
|
5950
|
+
}
|
|
5951
|
+
return word;
|
|
5952
|
+
}
|
|
5953
|
+
function lowerFirstLetter(value) {
|
|
5954
|
+
return value.replace(/\p{L}/u, (letter) => letter.toLocaleLowerCase('pt-BR'));
|
|
5955
|
+
}
|
|
5183
5956
|
|
|
5184
5957
|
var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
|
|
5185
5958
|
__proto__: null,
|
|
@@ -5229,6 +6002,7 @@ class PraxisCrudWidgetConfigEditor {
|
|
|
5229
6002
|
componentInstanceId: this.inputs?.componentInstanceId ?? this.widgetKey,
|
|
5230
6003
|
context: this.inputs?.context,
|
|
5231
6004
|
enableCustomization: this.inputs?.enableCustomization ?? false,
|
|
6005
|
+
authoringCapability: this.inputs?.authoringCapability ?? null,
|
|
5232
6006
|
};
|
|
5233
6007
|
return {
|
|
5234
6008
|
inputs,
|
|
@@ -5325,11 +6099,31 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5325
6099
|
type: 'PraxisDataQueryContext | null',
|
|
5326
6100
|
description: 'Contexto semântico de consulta encaminhado para a tabela interna do CRUD. Preferir este contrato para novo authoring remoto.',
|
|
5327
6101
|
},
|
|
6102
|
+
{
|
|
6103
|
+
name: 'metadata.resource.schemaPath',
|
|
6104
|
+
type: 'string | null',
|
|
6105
|
+
description: 'Template canonico opcional usado apenas para resolver /schemas/filtered quando resource.path precisa ser um alvo operacional concreto.',
|
|
6106
|
+
},
|
|
6107
|
+
{
|
|
6108
|
+
name: 'metadata.resource.title',
|
|
6109
|
+
type: 'string | null',
|
|
6110
|
+
description: 'Titulo documental do recurso usado pelo shell CRUD para derivar titulos contextuais de criacao, edicao e visualizacao quando a action nao informa label.',
|
|
6111
|
+
},
|
|
6112
|
+
{
|
|
6113
|
+
name: 'metadata.resource.formTitle',
|
|
6114
|
+
type: 'string | null',
|
|
6115
|
+
description: 'Titulo documental especifico para formularios do recurso, usado antes de metadata.resource.title no shell modal/drawer.',
|
|
6116
|
+
},
|
|
5328
6117
|
{
|
|
5329
6118
|
name: 'metadata.filterCriteria',
|
|
5330
6119
|
type: 'Record<string, unknown> | null',
|
|
5331
6120
|
description: 'Bridge declarativa de filtros encaminhada para a tabela interna. Para novo authoring, prefira metadata.queryContext.',
|
|
5332
6121
|
},
|
|
6122
|
+
{
|
|
6123
|
+
name: 'metadata.form',
|
|
6124
|
+
type: 'FormConfig',
|
|
6125
|
+
description: 'Configuração do formulário encaminhada para o PraxisDynamicForm aberto por ações modal/drawer; layout, fieldMetadata, actions e helpPresentation permanecem propriedade do Dynamic Form.',
|
|
6126
|
+
},
|
|
5333
6127
|
{
|
|
5334
6128
|
name: 'metadata.actions[].form',
|
|
5335
6129
|
type: 'CrudActionFormContract',
|
|
@@ -5341,6 +6135,12 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5341
6135
|
default: false,
|
|
5342
6136
|
description: 'Habilita modo de customização do layout.',
|
|
5343
6137
|
},
|
|
6138
|
+
{
|
|
6139
|
+
name: 'authoringCapability',
|
|
6140
|
+
type: 'string | null',
|
|
6141
|
+
default: null,
|
|
6142
|
+
description: 'Capability publica do contexto corporativo exigida para disponibilizar authoring governado no CRUD e na tabela interna.',
|
|
6143
|
+
},
|
|
5344
6144
|
],
|
|
5345
6145
|
outputs: [
|
|
5346
6146
|
{
|
|
@@ -5699,6 +6499,7 @@ const CRUD_AI_CAPABILITIES = {
|
|
|
5699
6499
|
{ path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
|
|
5700
6500
|
{ path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
|
|
5701
6501
|
{ path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
|
|
6502
|
+
{ path: 'actions[].form.layoutPolicy', category: 'actions', valueKind: 'object', description: 'Politica schema-driven opt-in encaminhada ao Dynamic Form para materializar detalhe ou command form sem FormConfig.sections local; deve acompanhar schemaUrl compativel, especialmente schemaType=request para comandos.' },
|
|
5702
6503
|
{ path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
|
|
5703
6504
|
{ path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
|
|
5704
6505
|
{ path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
|
|
@@ -5736,6 +6537,7 @@ const surfaceConfigureSchema = {
|
|
|
5736
6537
|
submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
|
|
5737
6538
|
apiEndpointKey: { type: 'string' },
|
|
5738
6539
|
initialValue: { type: 'object' },
|
|
6540
|
+
layoutPolicy: { type: 'object' },
|
|
5739
6541
|
},
|
|
5740
6542
|
},
|
|
5741
6543
|
params: {
|
|
@@ -5789,14 +6591,28 @@ const dialogHostSchema = {
|
|
|
5789
6591
|
width: { type: 'string' },
|
|
5790
6592
|
height: { type: 'string' },
|
|
5791
6593
|
minWidth: { type: 'string' },
|
|
6594
|
+
minHeight: { type: 'string' },
|
|
5792
6595
|
maxWidth: { type: 'string' },
|
|
6596
|
+
maxHeight: { type: 'string' },
|
|
5793
6597
|
density: { type: 'string' },
|
|
6598
|
+
backdropStyle: { enum: ['blur', 'dim', 'transparent'] },
|
|
5794
6599
|
canMaximize: { type: 'boolean' },
|
|
5795
6600
|
rememberLastState: { type: 'boolean' },
|
|
6601
|
+
closeOnSave: { type: 'boolean' },
|
|
5796
6602
|
startMaximized: { type: 'boolean' },
|
|
5797
6603
|
disableCloseOnEsc: { type: 'boolean' },
|
|
5798
6604
|
disableCloseOnBackdrop: { type: 'boolean' },
|
|
5799
|
-
fullscreenBreakpoint: { type: '
|
|
6605
|
+
fullscreenBreakpoint: { type: 'number' },
|
|
6606
|
+
position: {
|
|
6607
|
+
type: 'object',
|
|
6608
|
+
properties: {
|
|
6609
|
+
top: { type: 'string' },
|
|
6610
|
+
bottom: { type: 'string' },
|
|
6611
|
+
left: { type: 'string' },
|
|
6612
|
+
right: { type: 'string' },
|
|
6613
|
+
},
|
|
6614
|
+
},
|
|
6615
|
+
edgeGap: { type: 'number' },
|
|
5800
6616
|
},
|
|
5801
6617
|
},
|
|
5802
6618
|
back: { type: 'object' },
|
|
@@ -5849,19 +6665,21 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5849
6665
|
{ name: 'crudId', type: 'string', description: 'Stable CRUD instance id used for table/form identity and persistence.' },
|
|
5850
6666
|
{ name: 'componentInstanceId', type: 'string', description: 'Optional stable host instance id for multiple CRUD widgets on the same route.' },
|
|
5851
6667
|
{ name: 'context', type: 'Record<string, unknown>', description: 'Opaque host context used for authoring seeds and launcher inputs.' },
|
|
6668
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Explicit host opt-in for CRUD authoring surfaces.' },
|
|
6669
|
+
{ name: 'authoringCapability', type: 'string | null', description: 'Public runtime capability required before CRUD and delegated table authoring can open.' },
|
|
5852
6670
|
{ name: 'afterOpen', type: '{ mode: FormOpenMode; action: string }', description: 'Emitted after a CRUD action opens.' },
|
|
5853
6671
|
{ name: 'afterSave', type: '{ id: string | number; data: unknown }', description: 'Emitted after save; CRUD refetches the list.' },
|
|
5854
6672
|
{ name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
|
|
5855
6673
|
],
|
|
5856
6674
|
editableTargets: [
|
|
5857
|
-
{ kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', description: 'Resource path/key, id field, endpoint key and query context owned by CRUD orchestration.' },
|
|
6675
|
+
{ kind: 'resourceBinding', resolver: 'crud-resource-by-path-or-key', description: 'Resource path/key, optional schema path template, id field, endpoint key and query context owned by CRUD orchestration.' },
|
|
5858
6676
|
{ kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
|
|
5859
6677
|
{ kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
|
|
5860
6678
|
{ kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
|
|
5861
6679
|
{ kind: 'viewSurface', resolver: 'crud-action-by-id:view', description: 'View action open mode, route/form binding and launcher inputs.' },
|
|
5862
6680
|
{ kind: 'deleteBehavior', resolver: 'crud-action-by-id:delete', description: 'Delete action enablement, confirmation, endpoint and capability policy.' },
|
|
5863
|
-
{ kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', description: 'Route/modal/drawer defaults consumed by CrudLauncherService and DynamicFormDialogHostComponent.' },
|
|
5864
|
-
{ kind: 'formBinding', resolver: 'crud-action-form-contract-by-action-id', description: 'CRUD-owned form binding fields: formId, schemaUrl, submitUrl, submitMethod, params and initialValue.' },
|
|
6681
|
+
{ kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', description: 'Route/modal/drawer defaults consumed by CrudLauncherService and DynamicFormDialogHostComponent, including contextual shell titles derived from action label, form title or resource metadata.' },
|
|
6682
|
+
{ kind: 'formBinding', resolver: 'crud-action-form-contract-by-action-id', description: 'CRUD-owned form binding fields: formId, schemaUrl, submitUrl, submitMethod, params and initialValue. Shared FormConfig remains owned by Dynamic Form and is forwarded through CrudMetadata.form when present.' },
|
|
5865
6683
|
{ kind: 'permissions', resolver: 'crud-resource-capabilities', description: 'CRUD action availability derived from resource capabilities and action permissions.' },
|
|
5866
6684
|
{ kind: 'domainGovernanceContext', resolver: 'domain-catalog-context-by-resource-key', description: 'Read-only semantic/governance context resolved from Domain Catalog and referenced from CRUD queryContext.meta.' },
|
|
5867
6685
|
{ kind: 'childOperation', resolver: 'child-authoring-manifest-operation', description: 'Delegated form/table/dialog/settings-panel operation owned by the child component manifest.' },
|
|
@@ -5877,13 +6695,13 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5877
6695
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
|
|
5878
6696
|
reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
|
|
5879
6697
|
writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
|
|
5880
|
-
identityKeys: ['resourcePath', 'resourceKey'],
|
|
6698
|
+
identityKeys: ['resourcePath', 'resourceKey', 'schemaPath'],
|
|
5881
6699
|
inputSchema: resourceBindSchema,
|
|
5882
6700
|
failureModes: ['resource-not-found', 'schema-url-not-canonical', 'capabilities-unavailable', 'id-field-missing'],
|
|
5883
|
-
description: 'Binds CRUD to a canonical resource and validates it against api_metadata/resource capabilities before runtime use.',
|
|
6701
|
+
description: 'Binds CRUD to a canonical resource and validates it against api_metadata/resource capabilities before runtime use. Use CrudMetadata.resource.schemaPath when schema discovery must use a templated path while resource.path targets a concrete nested collection.',
|
|
5884
6702
|
} }],
|
|
5885
6703
|
validators: ['resource-exists-in-api-metadata', 'resource-path-canonical', 'resource-key-stable', 'id-field-known', 'resource-capabilities-resolvable'],
|
|
5886
|
-
affectedPaths: ['resource.path', 'resource.idField', 'resource.endpointKey', 'queryContext', 'filterCriteria'],
|
|
6704
|
+
affectedPaths: ['resource.path', 'resource.schemaPath', 'resource.idField', 'resource.endpointKey', 'resource.apiUrlEntry', 'queryContext', 'filterCriteria'],
|
|
5887
6705
|
submissionImpact: 'affects-remote-binding',
|
|
5888
6706
|
preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
|
|
5889
6707
|
},
|
|
@@ -6017,14 +6835,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6017
6835
|
target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
|
|
6018
6836
|
inputSchema: dialogHostSchema,
|
|
6019
6837
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
|
|
6020
|
-
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
|
|
6838
|
+
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'DynamicFormDialogHostComponent', 'CRUD_DRAWER_ADAPTER'],
|
|
6021
6839
|
writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
|
|
6022
6840
|
identityKeys: ['crudId'],
|
|
6023
6841
|
inputSchema: dialogHostSchema,
|
|
6024
|
-
failureModes: ['open-mode-unsupported', 'drawer-
|
|
6025
|
-
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher
|
|
6842
|
+
failureModes: ['open-mode-unsupported', 'drawer-runtime-unavailable', 'modal-size-invalid', 'drawer-position-lifecycle-unstable', 'back-policy-invalid'],
|
|
6843
|
+
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher, default dialog-backed drawer runtime and optional host drawer adapter.',
|
|
6026
6844
|
} }],
|
|
6027
|
-
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-
|
|
6845
|
+
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-runtime-available', 'drawer-position-lifecycle-stable', 'back-policy-valid', 'settings-panel-shell-compatible'],
|
|
6028
6846
|
affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
|
|
6029
6847
|
submissionImpact: 'config-only',
|
|
6030
6848
|
preconditions: ['crud-metadata-loaded'],
|
|
@@ -6100,7 +6918,8 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6100
6918
|
{ validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
|
|
6101
6919
|
{ validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
|
|
6102
6920
|
{ validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
|
|
6103
|
-
{ validatorId: 'drawer-
|
|
6921
|
+
{ validatorId: 'drawer-runtime-available', level: 'error', code: 'CRUD_DRAWER_RUNTIME_AVAILABLE', description: 'Drawer open mode uses the default dialog-backed drawer runtime with CRUD-owned chrome, responsive right-panel sizing, Escape/backdrop handling and semantic close/save results; a host drawer adapter is optional only for shell-specific presentation.' },
|
|
6922
|
+
{ validatorId: 'drawer-position-lifecycle-stable', level: 'error', code: 'CRUD_DRAWER_POSITION_LIFECYCLE_STABLE', description: 'A drawer must preserve its configured viewport edge through open, remembered-size restoration, maximize and restore.' },
|
|
6104
6923
|
{ validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
|
|
6105
6924
|
{ validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
|
|
6106
6925
|
{ validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
|
|
@@ -6121,6 +6940,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6121
6940
|
'FormConfig and FieldMetadata edits must delegate to dynamic-form or metadata-editor manifests.',
|
|
6122
6941
|
'TableConfig edits must delegate to praxis-table; CRUD owns only shell orchestration and list surface binding.',
|
|
6123
6942
|
'Delete behavior is destructive and requires explicit confirmation plus backend capability support.',
|
|
6943
|
+
'Drawer position and edgeGap must round-trip without changing the configured edge during open, remembered-size restoration, maximize or restore.',
|
|
6124
6944
|
'crudContext must remain reference-stable across change detection cycles.',
|
|
6125
6945
|
],
|
|
6126
6946
|
examples: [
|
|
@@ -6132,6 +6952,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6132
6952
|
{ id: 'crud-view-drawer-readonly', request: 'Open view in a drawer and keep the form readonly.', operationId: 'surface.view.configure', target: 'action:view', params: { actionId: 'view', openMode: 'drawer', formId: 'employee-view' }, isPositive: true },
|
|
6133
6953
|
{ id: 'crud-delete-enable-confirmed', request: 'Enable delete but require confirmation.', operationId: 'delete.enabled.set', target: 'action:delete', params: { enabled: true, actionId: 'delete', requiresConfirmation: true, autoDelete: true }, isPositive: true },
|
|
6134
6954
|
{ id: 'crud-dialog-size-medium', request: 'Make CRUD modals 900px wide and remember their last state.', operationId: 'dialog.size.set', target: 'dialogHost', params: { defaultOpenMode: 'modal', modal: { width: '900px', rememberLastState: true } }, isPositive: true },
|
|
6955
|
+
{ id: 'crud-drawer-left-edge', request: 'Open CRUD forms in a left drawer with an 8px maximized inset.', operationId: 'dialog.size.set', target: 'dialogHost', params: { defaultOpenMode: 'drawer', modal: { position: { left: '0', top: '0' }, edgeGap: 8 } }, isPositive: true },
|
|
6135
6956
|
{ id: 'crud-permissions-no-delete', request: 'Hide delete when the resource capabilities do not allow it.', operationId: 'permissions.set', target: 'permissions:delete', params: { requiredCapabilities: ['delete'], denyWhenMissingCapability: true }, isPositive: true },
|
|
6136
6957
|
{ id: 'crud-delegate-form-layout', request: 'Add a new field to the create form.', operationId: 'form.childOperation.delegate', target: 'praxis-dynamic-form:field.add', params: { childComponentId: 'praxis-dynamic-form', childOperationId: 'field.add', reason: 'Form field semantics belong to dynamic-form' }, isPositive: true },
|
|
6137
6958
|
{ id: 'crud-delegate-table-column', request: 'Add a salary column to the list table.', operationId: 'form.childOperation.delegate', target: 'praxis-table:column.add', params: { childComponentId: 'praxis-table', childOperationId: 'column.add', reason: 'Table column semantics belong to praxis-table' }, isPositive: true },
|