@praxisui/crud 9.0.0-beta.8 → 9.0.0-beta.80
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 +60 -9
- package/ai/component-registry.json +11360 -0
- package/fesm2022/praxisui-crud.mjs +823 -81
- package/package.json +12 -8
- package/types/praxisui-crud.d.ts +57 -2
|
@@ -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, 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,29 @@ class CrudLauncherService {
|
|
|
123
137
|
row[idField] !== undefined) {
|
|
124
138
|
inputs[idField] = row[idField];
|
|
125
139
|
}
|
|
140
|
+
if (mode === 'drawer' && this.drawerAdapter) {
|
|
141
|
+
await Promise.resolve(this.drawerAdapter.open({
|
|
142
|
+
action: actionForLaunch,
|
|
143
|
+
metadata: merged.metadata,
|
|
144
|
+
inputs,
|
|
145
|
+
onClose: drawerCallbacks?.onClose,
|
|
146
|
+
onResult: drawerCallbacks?.onResult,
|
|
147
|
+
}));
|
|
148
|
+
return { mode };
|
|
149
|
+
}
|
|
126
150
|
const modalCfg = { ...(merged.metadata.defaults?.modal || {}) };
|
|
127
|
-
|
|
151
|
+
debugCrudLauncher('[CRUD:Launcher] opening dialog with:', {
|
|
128
152
|
action: merged.action.action,
|
|
129
153
|
formId: actionForLaunch.formId,
|
|
130
154
|
inputs,
|
|
131
155
|
modalCfg,
|
|
132
156
|
resourcePath: merged.metadata.resource?.path ?? merged.metadata.table?.resourcePath,
|
|
133
157
|
});
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
158
|
+
const drawerMode = mode === 'drawer';
|
|
159
|
+
const dialogPosition = drawerMode
|
|
160
|
+
? (modalCfg.position ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.position)
|
|
161
|
+
: modalCfg.position;
|
|
162
|
+
const panelClasses = mergeClassList(['pfx-dialog-pane', drawerMode ? 'pfx-drawer-pane' : 'pfx-dialog-frosted'], modalCfg.panelClass);
|
|
138
163
|
// Backdrop style presets
|
|
139
164
|
const backdropClasses = [];
|
|
140
165
|
const style = modalCfg.backdropStyle;
|
|
@@ -147,20 +172,58 @@ class CrudLauncherService {
|
|
|
147
172
|
else if (style === 'transparent') {
|
|
148
173
|
backdropClasses.push('pfx-transparent-backdrop');
|
|
149
174
|
}
|
|
150
|
-
|
|
151
|
-
backdropClasses.push(modalCfg.backdropClass);
|
|
152
|
-
}
|
|
175
|
+
const mergedBackdropClasses = mergeClassList(backdropClasses, modalCfg.backdropClass);
|
|
153
176
|
const ref = await this.dialog.openAsync(() => Promise.resolve().then(function () { return dynamicFormDialogHost_component; }).then((m) => m.DynamicFormDialogHostComponent), {
|
|
154
177
|
...modalCfg,
|
|
155
178
|
panelClass: panelClasses,
|
|
156
|
-
backdropClass:
|
|
179
|
+
backdropClass: mergedBackdropClasses,
|
|
157
180
|
autoFocus: modalCfg.autoFocus ?? true,
|
|
158
181
|
restoreFocus: modalCfg.restoreFocus ?? true,
|
|
159
|
-
minWidth:
|
|
160
|
-
|
|
182
|
+
minWidth: drawerMode
|
|
183
|
+
? (modalCfg.minWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.minWidth)
|
|
184
|
+
: '360px',
|
|
185
|
+
width: drawerMode
|
|
186
|
+
? (modalCfg.width ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.width)
|
|
187
|
+
: modalCfg.width,
|
|
188
|
+
height: drawerMode
|
|
189
|
+
? (modalCfg.height ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.height)
|
|
190
|
+
: modalCfg.height,
|
|
191
|
+
maxWidth: drawerMode
|
|
192
|
+
? (modalCfg.maxWidth ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.maxWidth)
|
|
193
|
+
: '95vw',
|
|
194
|
+
maxHeight: drawerMode
|
|
195
|
+
? (modalCfg.maxHeight ?? DEFAULT_CRUD_DRAWER_MODAL_CONFIG.maxHeight)
|
|
196
|
+
: modalCfg.maxHeight,
|
|
197
|
+
position: dialogPosition,
|
|
161
198
|
ariaLabelledBy: 'crudDialogTitle',
|
|
162
|
-
data: {
|
|
199
|
+
data: {
|
|
200
|
+
action: actionForLaunch,
|
|
201
|
+
row,
|
|
202
|
+
metadata: merged.metadata,
|
|
203
|
+
inputs,
|
|
204
|
+
presentation: drawerMode ? 'drawer' : 'modal',
|
|
205
|
+
dialogPosition,
|
|
206
|
+
resourceIdentity: drawerMode && actionForLaunch.action === 'edit'
|
|
207
|
+
? runtime?.resourceIdentity ?? null
|
|
208
|
+
: null,
|
|
209
|
+
},
|
|
163
210
|
});
|
|
211
|
+
if (drawerMode) {
|
|
212
|
+
ref
|
|
213
|
+
.afterClosed()
|
|
214
|
+
.pipe(take(1))
|
|
215
|
+
.subscribe((closedValue) => {
|
|
216
|
+
const result = toCrudDrawerResult(closedValue);
|
|
217
|
+
drawerCallbacks?.onClose?.();
|
|
218
|
+
if (result?.type) {
|
|
219
|
+
drawerCallbacks?.onResult?.(result);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
drawerCallbacks?.onResult?.({ type: 'close' });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return { mode, ref };
|
|
226
|
+
}
|
|
164
227
|
return { mode, ref };
|
|
165
228
|
}
|
|
166
229
|
resolveOpenMode(action, metadata) {
|
|
@@ -168,12 +231,17 @@ class CrudLauncherService {
|
|
|
168
231
|
const local = action.openMode ?? metadata.defaults?.openMode;
|
|
169
232
|
if (local)
|
|
170
233
|
return local;
|
|
171
|
-
// Global fallback (action-specific, then general)
|
|
234
|
+
// Global fallback (action-specific, then general). A canonical CRUD
|
|
235
|
+
// operation without an explicit route can still be materialized through
|
|
236
|
+
// the inferred form contract, so it must not fall through to a route that
|
|
237
|
+
// does not exist.
|
|
172
238
|
try {
|
|
173
239
|
const globalCrud = this.global.getCrud();
|
|
174
240
|
const actionName = action.action;
|
|
175
241
|
const globalMode = (actionName && globalCrud?.actionDefaults?.[actionName]?.openMode) ?? globalCrud?.defaults?.openMode;
|
|
176
|
-
let resolved = globalMode ??
|
|
242
|
+
let resolved = globalMode ?? (this.isCanonicalCrudAction(action.action) && !action.route
|
|
243
|
+
? 'drawer'
|
|
244
|
+
: 'route');
|
|
177
245
|
// Safety: if modal/drawer but there is no formId and the action cannot be inferred, degrade to route.
|
|
178
246
|
if ((resolved === 'modal' || resolved === 'drawer') &&
|
|
179
247
|
!action.formId &&
|
|
@@ -248,6 +316,9 @@ class CrudLauncherService {
|
|
|
248
316
|
if (resolved.apiUrlEntry != null) {
|
|
249
317
|
inputs['apiUrlEntry'] = resolved.apiUrlEntry;
|
|
250
318
|
}
|
|
319
|
+
if (resolved.layoutPolicy != null) {
|
|
320
|
+
inputs['layoutPolicy'] = resolved.layoutPolicy;
|
|
321
|
+
}
|
|
251
322
|
if (action.form?.initialValue != null) {
|
|
252
323
|
inputs['initialValue'] = action.form.initialValue;
|
|
253
324
|
}
|
|
@@ -257,6 +328,7 @@ class CrudLauncherService {
|
|
|
257
328
|
return inputs;
|
|
258
329
|
}
|
|
259
330
|
resolveActionForLaunch(action, metadata) {
|
|
331
|
+
action = this.normalizeActionForLaunch(action);
|
|
260
332
|
if (action.formId) {
|
|
261
333
|
return action;
|
|
262
334
|
}
|
|
@@ -264,29 +336,61 @@ class CrudLauncherService {
|
|
|
264
336
|
return action;
|
|
265
337
|
}
|
|
266
338
|
const formId = this.buildInferredFormId(action, metadata);
|
|
267
|
-
return formId
|
|
339
|
+
return formId
|
|
340
|
+
? {
|
|
341
|
+
...action,
|
|
342
|
+
formId,
|
|
343
|
+
form: {
|
|
344
|
+
...(action.form || {}),
|
|
345
|
+
layoutPolicy: action.form?.layoutPolicy ?? this.defaultInferredLayoutPolicy(action),
|
|
346
|
+
},
|
|
347
|
+
}
|
|
348
|
+
: action;
|
|
349
|
+
}
|
|
350
|
+
resolveSubmitUrlTemplate(submitUrl, row, metadata) {
|
|
351
|
+
const template = String(submitUrl || '').trim();
|
|
352
|
+
if (!template || !template.includes('{')) {
|
|
353
|
+
return template;
|
|
354
|
+
}
|
|
355
|
+
const idField = String(metadata.resource?.idField ?? 'id');
|
|
356
|
+
const rowId = row?.[idField];
|
|
357
|
+
if (rowId == null) {
|
|
358
|
+
return template;
|
|
359
|
+
}
|
|
360
|
+
const encodedId = encodeURIComponent(String(rowId));
|
|
361
|
+
const idFieldPattern = new RegExp(`\\{${escapeRegExp(idField)}\\}`, 'g');
|
|
362
|
+
return template
|
|
363
|
+
.replace(idFieldPattern, encodedId)
|
|
364
|
+
.replace(/\{id\}/g, encodedId)
|
|
365
|
+
.replace(/\{resourceId\}/g, encodedId);
|
|
268
366
|
}
|
|
269
367
|
resolveActionFormContract(action, row, metadata, runtime) {
|
|
270
368
|
if (this.isExplicitCrudAction(action)) {
|
|
271
369
|
return {
|
|
272
370
|
schemaUrl: action.form?.schemaUrl ?? null,
|
|
273
|
-
submitUrl: action.form?.submitUrl
|
|
371
|
+
submitUrl: action.form?.submitUrl
|
|
372
|
+
? this.resolveSubmitUrlTemplate(action.form.submitUrl, row, metadata)
|
|
373
|
+
: null,
|
|
274
374
|
submitMethod: action.form?.submitMethod ?? null,
|
|
275
375
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
276
376
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
377
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
277
378
|
};
|
|
278
379
|
}
|
|
279
380
|
if (!this.isCanonicalCrudAction(action.action)) {
|
|
280
381
|
return {
|
|
281
382
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
282
383
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
384
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
283
385
|
};
|
|
284
386
|
}
|
|
285
387
|
const resourcePath = String(metadata.resource?.path ?? metadata.table?.resourcePath ?? '').trim();
|
|
388
|
+
const schemaResourcePath = String(metadata.resource?.schemaPath || '').trim() || null;
|
|
286
389
|
if (!resourcePath) {
|
|
287
390
|
return {
|
|
288
391
|
apiEndpointKey: action.form?.apiEndpointKey ?? null,
|
|
289
392
|
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
393
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
290
394
|
};
|
|
291
395
|
}
|
|
292
396
|
const idField = String(metadata.resource?.idField ?? 'id');
|
|
@@ -294,6 +398,7 @@ class CrudLauncherService {
|
|
|
294
398
|
const resolved = this.operationResolver.resolve({
|
|
295
399
|
operation: action.action,
|
|
296
400
|
resourcePath,
|
|
401
|
+
schemaResourcePath,
|
|
297
402
|
resourceId: resourceId ?? null,
|
|
298
403
|
capabilities: runtime?.capabilities ?? null,
|
|
299
404
|
links: runtime?.links ?? null,
|
|
@@ -301,14 +406,15 @@ class CrudLauncherService {
|
|
|
301
406
|
endpointKey: action.form?.apiEndpointKey ??
|
|
302
407
|
metadata.resource?.endpointKey ??
|
|
303
408
|
undefined,
|
|
304
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
409
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
305
410
|
});
|
|
306
411
|
return {
|
|
307
412
|
schemaUrl: resolved?.schemaUrl ?? null,
|
|
308
413
|
submitUrl: resolved?.submitUrl ?? null,
|
|
309
414
|
submitMethod: resolved?.submitMethod ?? null,
|
|
310
415
|
apiEndpointKey: action.form?.apiEndpointKey ?? metadata.resource?.endpointKey ?? null,
|
|
311
|
-
apiUrlEntry: action.form?.apiUrlEntry ?? null,
|
|
416
|
+
apiUrlEntry: action.form?.apiUrlEntry ?? metadata.resource?.apiUrlEntry ?? null,
|
|
417
|
+
layoutPolicy: action.form?.layoutPolicy ?? null,
|
|
312
418
|
};
|
|
313
419
|
}
|
|
314
420
|
resolveRuntimeContract(action, row, metadata, runtime) {
|
|
@@ -327,9 +433,20 @@ class CrudLauncherService {
|
|
|
327
433
|
return `${sanitizedResource || 'crud'}-${String(action.action || '').trim().toLowerCase()}`;
|
|
328
434
|
}
|
|
329
435
|
isCanonicalCrudAction(actionName) {
|
|
330
|
-
const normalized =
|
|
436
|
+
const normalized = this.normalizeCrudOperationName(actionName);
|
|
331
437
|
return normalized === 'create' || normalized === 'view' || normalized === 'edit' || normalized === 'delete';
|
|
332
438
|
}
|
|
439
|
+
normalizeActionForLaunch(action) {
|
|
440
|
+
const normalized = this.normalizeCrudOperationName(action.action);
|
|
441
|
+
return normalized === action.action ? action : { ...action, action: normalized };
|
|
442
|
+
}
|
|
443
|
+
normalizeCrudOperationName(actionName) {
|
|
444
|
+
const normalized = String(actionName || '').trim().toLowerCase();
|
|
445
|
+
if (['add', 'novo', 'new', 'incluir', 'inserir'].includes(normalized)) {
|
|
446
|
+
return 'create';
|
|
447
|
+
}
|
|
448
|
+
return normalized;
|
|
449
|
+
}
|
|
333
450
|
isExplicitCrudAction(action) {
|
|
334
451
|
if (action.mode === 'explicit') {
|
|
335
452
|
return true;
|
|
@@ -338,6 +455,19 @@ class CrudLauncherService {
|
|
|
338
455
|
String(action.form?.submitUrl || '').trim() ||
|
|
339
456
|
String(action.form?.submitMethod || '').trim());
|
|
340
457
|
}
|
|
458
|
+
defaultInferredLayoutPolicy(action) {
|
|
459
|
+
const operation = this.normalizeCrudOperationName(action.action);
|
|
460
|
+
if (operation !== 'create' && operation !== 'edit') {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
source: 'schema',
|
|
465
|
+
intent: 'command',
|
|
466
|
+
preset: 'groupedCommand',
|
|
467
|
+
persistence: 'transient',
|
|
468
|
+
schemaType: 'request',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
341
471
|
async mergeCrudOverrides(metadata, action, componentKeyId) {
|
|
342
472
|
try {
|
|
343
473
|
if (!componentKeyId)
|
|
@@ -392,6 +522,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
392
522
|
type: Injectable,
|
|
393
523
|
args: [{ providedIn: 'root' }]
|
|
394
524
|
}] });
|
|
525
|
+
function mergeClassList(base, extra) {
|
|
526
|
+
if (!extra) {
|
|
527
|
+
return base;
|
|
528
|
+
}
|
|
529
|
+
if (typeof extra === 'string') {
|
|
530
|
+
return [...base, extra];
|
|
531
|
+
}
|
|
532
|
+
if (Array.isArray(extra)) {
|
|
533
|
+
return [...base, ...extra];
|
|
534
|
+
}
|
|
535
|
+
return [
|
|
536
|
+
...base,
|
|
537
|
+
...Object.keys(extra).filter((className) => !!extra[className]),
|
|
538
|
+
];
|
|
539
|
+
}
|
|
540
|
+
function escapeRegExp(value) {
|
|
541
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
542
|
+
}
|
|
543
|
+
function toCrudDrawerResult(value) {
|
|
544
|
+
if (!value || typeof value !== 'object') {
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
const result = value;
|
|
548
|
+
return result.type === 'save' || result.type === 'delete' || result.type === 'close'
|
|
549
|
+
? result
|
|
550
|
+
: undefined;
|
|
551
|
+
}
|
|
395
552
|
|
|
396
553
|
const DOCUMENT_KIND = 'praxis.crud.editor';
|
|
397
554
|
const DOCUMENT_VERSION = 1;
|
|
@@ -510,10 +667,15 @@ function normalizeCrudMetadata(metadata) {
|
|
|
510
667
|
if (base.resource && typeof base.resource === 'object') {
|
|
511
668
|
normalized.resource = stripUndefinedShallow({
|
|
512
669
|
path: trimString(base.resource.path) || undefined,
|
|
670
|
+
schemaPath: trimString(base.resource.schemaPath) || undefined,
|
|
513
671
|
idField: typeof base.resource.idField === 'string'
|
|
514
672
|
? trimString(base.resource.idField) || undefined
|
|
515
673
|
: base.resource.idField,
|
|
674
|
+
title: trimString(base.resource.title) || undefined,
|
|
675
|
+
label: trimString(base.resource.label) || undefined,
|
|
676
|
+
formTitle: trimString(base.resource.formTitle) || undefined,
|
|
516
677
|
endpointKey: trimString(base.resource.endpointKey) || undefined,
|
|
678
|
+
apiUrlEntry: base.resource.apiUrlEntry || undefined,
|
|
517
679
|
});
|
|
518
680
|
}
|
|
519
681
|
if (base.defaults && typeof base.defaults === 'object') {
|
|
@@ -564,6 +726,7 @@ function normalizeCrudAction(action) {
|
|
|
564
726
|
apiEndpointKey: trimString(action.form.apiEndpointKey) || undefined,
|
|
565
727
|
apiUrlEntry: action.form.apiUrlEntry || undefined,
|
|
566
728
|
initialValue: normalizeInitialValue(action.form.initialValue),
|
|
729
|
+
layoutPolicy: action.form.layoutPolicy ?? undefined,
|
|
567
730
|
}
|
|
568
731
|
: undefined,
|
|
569
732
|
});
|
|
@@ -721,6 +884,11 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
721
884
|
'crud.emptyState.title': 'Conecte o CRUD a um recurso',
|
|
722
885
|
'crud.emptyState.description': 'Informe os metadados (resourcePath / schema) ou forneça metadata.data para habilitar a tabela e as ações.',
|
|
723
886
|
'crud.emptyState.primaryAction': 'Configurar metadados',
|
|
887
|
+
'crud.table.emptyState.initial.title': 'Sem registros em {label}',
|
|
888
|
+
'crud.table.emptyState.initial.titleFallback': 'Nenhum registro disponível.',
|
|
889
|
+
'crud.table.emptyState.initial.descriptionWithAction': 'Use a ação principal para adicionar o primeiro registro quando houver informações para cadastrar.',
|
|
890
|
+
'crud.table.emptyState.filtered.title': 'Nenhum resultado encontrado.',
|
|
891
|
+
'crud.table.emptyState.filtered.description': 'Revise os filtros ou ajuste o termo de busca.',
|
|
724
892
|
'crud.preferences.resetSuccess': 'Overrides de CRUD redefinidos',
|
|
725
893
|
'crud.actions.create': 'Adicionar',
|
|
726
894
|
'crud.actions.view': 'Ver',
|
|
@@ -733,6 +901,11 @@ const PRAXIS_CRUD_RUNTIME_I18N_CONFIG = {
|
|
|
733
901
|
'crud.emptyState.title': 'Connect CRUD to a resource',
|
|
734
902
|
'crud.emptyState.description': 'Provide metadata (resourcePath / schema) or metadata.data to enable the table and actions.',
|
|
735
903
|
'crud.emptyState.primaryAction': 'Configure metadata',
|
|
904
|
+
'crud.table.emptyState.initial.title': 'No records in {label}',
|
|
905
|
+
'crud.table.emptyState.initial.titleFallback': 'No records available.',
|
|
906
|
+
'crud.table.emptyState.initial.descriptionWithAction': 'Use the primary action to add the first record when there is information to register.',
|
|
907
|
+
'crud.table.emptyState.filtered.title': 'No results found.',
|
|
908
|
+
'crud.table.emptyState.filtered.description': 'Review the filters or adjust the search term.',
|
|
736
909
|
'crud.preferences.resetSuccess': 'CRUD overrides reset',
|
|
737
910
|
'crud.actions.create': 'Add',
|
|
738
911
|
'crud.actions.view': 'View',
|
|
@@ -3467,6 +3640,8 @@ class PraxisCrudComponent {
|
|
|
3467
3640
|
componentInstanceId;
|
|
3468
3641
|
context;
|
|
3469
3642
|
enableCustomization = false;
|
|
3643
|
+
/** Capability publica exigida para authoring governado do CRUD e da tabela interna. */
|
|
3644
|
+
authoringCapability = null;
|
|
3470
3645
|
configureRequested = new EventEmitter();
|
|
3471
3646
|
afterOpen = new EventEmitter();
|
|
3472
3647
|
afterClose = new EventEmitter();
|
|
@@ -3491,6 +3666,7 @@ class PraxisCrudComponent {
|
|
|
3491
3666
|
table;
|
|
3492
3667
|
storage = inject(ASYNC_CONFIG_STORAGE);
|
|
3493
3668
|
settingsPanel = inject(SettingsPanelService);
|
|
3669
|
+
enterpriseRuntimeContext = inject(EnterpriseRuntimeContextService, { optional: true });
|
|
3494
3670
|
snack = inject(MatSnackBar);
|
|
3495
3671
|
dialog = inject(DialogService);
|
|
3496
3672
|
i18n = inject(PraxisI18nService);
|
|
@@ -3498,6 +3674,7 @@ class PraxisCrudComponent {
|
|
|
3498
3674
|
resourceDiscoveryInstance;
|
|
3499
3675
|
actionOpenAdapterInstance;
|
|
3500
3676
|
surfaceOpenAdapterInstance;
|
|
3677
|
+
materializedRefreshSequence = 0;
|
|
3501
3678
|
global = (() => {
|
|
3502
3679
|
try {
|
|
3503
3680
|
return inject(GlobalConfigService);
|
|
@@ -3507,6 +3684,7 @@ class PraxisCrudComponent {
|
|
|
3507
3684
|
}
|
|
3508
3685
|
})();
|
|
3509
3686
|
surfaceService = inject(GLOBAL_SURFACE_SERVICE, { optional: true });
|
|
3687
|
+
surfaceOutlets = inject(SurfaceOutletRegistryService);
|
|
3510
3688
|
componentKeys = inject(ComponentKeyService);
|
|
3511
3689
|
route = (() => {
|
|
3512
3690
|
try {
|
|
@@ -3523,10 +3701,23 @@ class PraxisCrudComponent {
|
|
|
3523
3701
|
lastAppliedResourceIdentity = null;
|
|
3524
3702
|
tableCollectionLinks = null;
|
|
3525
3703
|
collectionCapabilities = null;
|
|
3704
|
+
activeAuthoringPanelRef;
|
|
3705
|
+
constructor() {
|
|
3706
|
+
this.enterpriseRuntimeContext?.contextChanges$
|
|
3707
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3708
|
+
.subscribe(() => {
|
|
3709
|
+
if (!this.isCustomizationAvailable()) {
|
|
3710
|
+
this.activeAuthoringPanelRef?.close();
|
|
3711
|
+
this.activeAuthoringPanelRef = undefined;
|
|
3712
|
+
}
|
|
3713
|
+
this.cdr.markForCheck();
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3526
3716
|
collectionCapabilitiesRequestHref = null;
|
|
3527
3717
|
collectionCapabilitiesResolvedHref = null;
|
|
3528
3718
|
collectionCapabilitiesRequestSeq = 0;
|
|
3529
3719
|
currentAuthoringDocument;
|
|
3720
|
+
selectedRow = null;
|
|
3530
3721
|
getResourceDiscovery() {
|
|
3531
3722
|
const assigned = this.resourceDiscovery;
|
|
3532
3723
|
return assigned ?? (this.resourceDiscoveryInstance ??= this.injector.get(ResourceDiscoveryService));
|
|
@@ -3553,15 +3744,32 @@ class PraxisCrudComponent {
|
|
|
3553
3744
|
catch { }
|
|
3554
3745
|
}
|
|
3555
3746
|
onTableRowClick(event) {
|
|
3747
|
+
const row = this.extractRowFromEvent(event);
|
|
3748
|
+
if (row) {
|
|
3749
|
+
this.selectedRow = row;
|
|
3750
|
+
}
|
|
3556
3751
|
this.rowClick.emit(event);
|
|
3557
3752
|
}
|
|
3558
3753
|
onTableSelectionChange(event) {
|
|
3754
|
+
this.selectedRow = this.extractSelectedRowFromEvent(event);
|
|
3559
3755
|
this.selectionChange.emit(event);
|
|
3560
3756
|
}
|
|
3757
|
+
async onBulkAction(event) {
|
|
3758
|
+
const action = String(event?.action || '').trim();
|
|
3759
|
+
if (!action) {
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
const row = this.extractSelectedRowFromBulkAction(event);
|
|
3763
|
+
if (row) {
|
|
3764
|
+
this.selectedRow = row;
|
|
3765
|
+
}
|
|
3766
|
+
await this.onAction(action, row ?? undefined, event);
|
|
3767
|
+
}
|
|
3561
3768
|
ngOnChanges(changes) {
|
|
3562
3769
|
if (!changes['metadata'] && !changes['context']) {
|
|
3563
3770
|
return;
|
|
3564
3771
|
}
|
|
3772
|
+
this.materializedRefreshSequence += 1;
|
|
3565
3773
|
try {
|
|
3566
3774
|
const parsed = typeof this.metadata === 'string'
|
|
3567
3775
|
? JSON.parse(this.metadata)
|
|
@@ -3595,33 +3803,46 @@ class PraxisCrudComponent {
|
|
|
3595
3803
|
async onAction(action, row, runtimeEvent) {
|
|
3596
3804
|
try {
|
|
3597
3805
|
document.activeElement?.blur();
|
|
3598
|
-
|
|
3806
|
+
const normalizedAction = this.normalizeCrudActionName(action);
|
|
3807
|
+
const contextualRow = row ?? this.resolveSelectedRowForAction(normalizedAction);
|
|
3808
|
+
let actionMeta = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3809
|
+
if (!actionMeta && normalizedAction !== action) {
|
|
3810
|
+
actionMeta = this.resolvedMetadata.actions?.find((candidate) => candidate.action === action);
|
|
3811
|
+
}
|
|
3599
3812
|
if (!actionMeta) {
|
|
3600
|
-
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => candidate.action ===
|
|
3813
|
+
const ctxAction = this.tableCrudContext?.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === normalizedAction);
|
|
3601
3814
|
if (ctxAction) {
|
|
3602
3815
|
actionMeta = {
|
|
3603
|
-
action: ctxAction.action,
|
|
3816
|
+
action: this.normalizeCrudActionName(ctxAction.action),
|
|
3604
3817
|
openMode: ctxAction.openMode,
|
|
3605
3818
|
formId: ctxAction.formId,
|
|
3606
3819
|
route: ctxAction.route,
|
|
3607
3820
|
};
|
|
3608
3821
|
}
|
|
3609
3822
|
else {
|
|
3610
|
-
actionMeta = { action };
|
|
3823
|
+
actionMeta = { action: normalizedAction };
|
|
3611
3824
|
}
|
|
3612
3825
|
}
|
|
3613
|
-
const effectiveAction =
|
|
3826
|
+
const effectiveAction = {
|
|
3827
|
+
...(actionMeta || { action: normalizedAction }),
|
|
3828
|
+
action: this.normalizeCrudActionName(actionMeta?.action ?? normalizedAction),
|
|
3829
|
+
};
|
|
3830
|
+
const resourceIdentity = runtimeEvent?.resourceIdentity ?? null;
|
|
3831
|
+
const handledByDuplicateDraft = await this.tryHandleCanonicalDuplicateDraftAction(effectiveAction, contextualRow);
|
|
3832
|
+
if (handledByDuplicateDraft) {
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3614
3835
|
if (!this.hasExplicitOpenBinding(effectiveAction)) {
|
|
3615
|
-
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(action,
|
|
3836
|
+
const openedByDiscovery = await this.tryOpenDiscoveredCrudSurface(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3616
3837
|
if (openedByDiscovery) {
|
|
3617
3838
|
return;
|
|
3618
3839
|
}
|
|
3619
|
-
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(action,
|
|
3840
|
+
const handledByWorkflowAction = await this.tryOpenDiscoveredWorkflowAction(effectiveAction.action, contextualRow, runtimeEvent);
|
|
3620
3841
|
if (handledByWorkflowAction) {
|
|
3621
3842
|
return;
|
|
3622
3843
|
}
|
|
3623
3844
|
}
|
|
3624
|
-
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction,
|
|
3845
|
+
const handledByDelete = await this.tryHandleCanonicalDeleteAction(effectiveAction, contextualRow);
|
|
3625
3846
|
if (handledByDelete) {
|
|
3626
3847
|
return;
|
|
3627
3848
|
}
|
|
@@ -3632,7 +3853,7 @@ class PraxisCrudComponent {
|
|
|
3632
3853
|
drawerCloseEmitted = true;
|
|
3633
3854
|
this.afterClose.emit();
|
|
3634
3855
|
};
|
|
3635
|
-
const { mode, ref } = await this.launcher.launch(effectiveAction,
|
|
3856
|
+
const { mode, ref } = await this.launcher.launch(effectiveAction, contextualRow, this.resolvedMetadata, this.componentKeyId(), {
|
|
3636
3857
|
onClose: () => emitDrawerClose(),
|
|
3637
3858
|
onResult: (result) => {
|
|
3638
3859
|
emitDrawerClose();
|
|
@@ -3653,9 +3874,13 @@ class PraxisCrudComponent {
|
|
|
3653
3874
|
capabilities: effectiveAction.action === 'create'
|
|
3654
3875
|
? this.collectionCapabilities
|
|
3655
3876
|
: this.collectionCapabilities,
|
|
3656
|
-
links: this.resolveCrudRuntimeLinks(effectiveAction.action,
|
|
3877
|
+
links: this.resolveCrudRuntimeLinks(effectiveAction.action, contextualRow),
|
|
3878
|
+
resourceIdentity,
|
|
3657
3879
|
});
|
|
3658
3880
|
this.afterOpen.emit({ mode, action: effectiveAction.action });
|
|
3881
|
+
if (mode === 'drawer') {
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3659
3884
|
if (!ref) {
|
|
3660
3885
|
return;
|
|
3661
3886
|
}
|
|
@@ -3791,6 +4016,9 @@ class PraxisCrudComponent {
|
|
|
3791
4016
|
return this.tx('crud.emptyState.primaryAction', 'Configure metadata');
|
|
3792
4017
|
}
|
|
3793
4018
|
onConfigureRequested() {
|
|
4019
|
+
if (!this.isCustomizationAvailable()) {
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
3794
4022
|
this.configureRequested.emit();
|
|
3795
4023
|
this.openCrudAuthoringFromTable();
|
|
3796
4024
|
}
|
|
@@ -3841,9 +4069,140 @@ class PraxisCrudComponent {
|
|
|
3841
4069
|
this.refreshTable();
|
|
3842
4070
|
return true;
|
|
3843
4071
|
}
|
|
4072
|
+
async tryHandleCanonicalDuplicateDraftAction(action, row) {
|
|
4073
|
+
const normalizedAction = this.normalizeCrudActionName(action.action);
|
|
4074
|
+
if (normalizedAction !== 'duplicate-draft' || !row) {
|
|
4075
|
+
return false;
|
|
4076
|
+
}
|
|
4077
|
+
const duplicateDraftUrl = this.getResourceDiscovery().resolveLinkHref(row['_links'], 'duplicate-draft', this.buildDiscoveryOptions());
|
|
4078
|
+
if (!duplicateDraftUrl) {
|
|
4079
|
+
return false;
|
|
4080
|
+
}
|
|
4081
|
+
const response = await firstValueFrom(this.http.post(duplicateDraftUrl, {}));
|
|
4082
|
+
const draft = this.unwrapRestData(response);
|
|
4083
|
+
if (!this.isRecord(draft)) {
|
|
4084
|
+
throw new Error('Duplicate draft action returned an invalid create draft.');
|
|
4085
|
+
}
|
|
4086
|
+
const configuredCreateAction = this.resolvedMetadata.actions?.find((candidate) => this.normalizeCrudActionName(candidate.action) === 'create') ?? { action: 'create' };
|
|
4087
|
+
const createAction = {
|
|
4088
|
+
...configuredCreateAction,
|
|
4089
|
+
action: 'create',
|
|
4090
|
+
openMode: configuredCreateAction.openMode ??
|
|
4091
|
+
action.openMode ??
|
|
4092
|
+
this.resolvedMetadata.defaults?.openMode ??
|
|
4093
|
+
'drawer',
|
|
4094
|
+
form: {
|
|
4095
|
+
...(configuredCreateAction.form ?? {}),
|
|
4096
|
+
initialValue: { ...draft },
|
|
4097
|
+
},
|
|
4098
|
+
};
|
|
4099
|
+
let drawerCloseEmitted = false;
|
|
4100
|
+
const emitDrawerClose = () => {
|
|
4101
|
+
if (drawerCloseEmitted)
|
|
4102
|
+
return;
|
|
4103
|
+
drawerCloseEmitted = true;
|
|
4104
|
+
this.afterClose.emit();
|
|
4105
|
+
};
|
|
4106
|
+
const handleResult = (result) => {
|
|
4107
|
+
if (result?.type !== 'save') {
|
|
4108
|
+
return;
|
|
4109
|
+
}
|
|
4110
|
+
const data = this.isRecord(result.data) ? result.data : {};
|
|
4111
|
+
const id = data[this.getIdField()];
|
|
4112
|
+
this.afterSave.emit({ id, data });
|
|
4113
|
+
this.refreshTable();
|
|
4114
|
+
};
|
|
4115
|
+
const { mode, ref } = await this.launcher.launch(createAction, undefined, this.resolvedMetadata, this.componentKeyId(), {
|
|
4116
|
+
onClose: () => emitDrawerClose(),
|
|
4117
|
+
onResult: (result) => handleResult(result),
|
|
4118
|
+
}, {
|
|
4119
|
+
capabilities: this.collectionCapabilities,
|
|
4120
|
+
links: this.tableCollectionLinks,
|
|
4121
|
+
});
|
|
4122
|
+
this.afterOpen.emit({ mode, action: normalizedAction });
|
|
4123
|
+
if (mode !== 'drawer' && ref) {
|
|
4124
|
+
ref
|
|
4125
|
+
.afterClosed()
|
|
4126
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4127
|
+
.subscribe((result) => {
|
|
4128
|
+
this.afterClose.emit();
|
|
4129
|
+
handleResult(result);
|
|
4130
|
+
});
|
|
4131
|
+
}
|
|
4132
|
+
return true;
|
|
4133
|
+
}
|
|
3844
4134
|
refreshTable() {
|
|
4135
|
+
if (this.tryRefreshMaterializedLocalData()) {
|
|
4136
|
+
return;
|
|
4137
|
+
}
|
|
3845
4138
|
this.table.refetch();
|
|
3846
4139
|
}
|
|
4140
|
+
tryRefreshMaterializedLocalData() {
|
|
4141
|
+
const readUrl = this.resolveMaterializedReadUrl();
|
|
4142
|
+
if (!readUrl || this.resolveResourcePath(this.resolvedMetadata)) {
|
|
4143
|
+
return false;
|
|
4144
|
+
}
|
|
4145
|
+
const refreshSequence = ++this.materializedRefreshSequence;
|
|
4146
|
+
void firstValueFrom(this.getResourceDiscovery().fetchJson(readUrl, {
|
|
4147
|
+
endpointKey: this.resolvedMetadata.resource?.endpointKey,
|
|
4148
|
+
apiUrlEntry: this.resolvedMetadata.resource?.apiUrlEntry,
|
|
4149
|
+
}))
|
|
4150
|
+
.then((response) => {
|
|
4151
|
+
if (!this.isCurrentMaterializedRefresh(refreshSequence, readUrl)) {
|
|
4152
|
+
return;
|
|
4153
|
+
}
|
|
4154
|
+
const data = this.extractCollectionData(this.unwrapRestData(response));
|
|
4155
|
+
if (!data) {
|
|
4156
|
+
this.error.emit({
|
|
4157
|
+
code: 'CRUD_MATERIALIZED_REFRESH_INVALID_COLLECTION',
|
|
4158
|
+
readUrl,
|
|
4159
|
+
});
|
|
4160
|
+
return;
|
|
4161
|
+
}
|
|
4162
|
+
this.resolvedMetadata = {
|
|
4163
|
+
...this.resolvedMetadata,
|
|
4164
|
+
data,
|
|
4165
|
+
};
|
|
4166
|
+
this.applyResolvedCrudState(this.resolvedMetadata);
|
|
4167
|
+
this.cdr.markForCheck();
|
|
4168
|
+
})
|
|
4169
|
+
.catch((error) => this.error.emit(error));
|
|
4170
|
+
return true;
|
|
4171
|
+
}
|
|
4172
|
+
isCurrentMaterializedRefresh(refreshSequence, readUrl) {
|
|
4173
|
+
return (refreshSequence === this.materializedRefreshSequence &&
|
|
4174
|
+
this.resolveMaterializedReadUrl() === readUrl &&
|
|
4175
|
+
this.resolveResourcePath(this.resolvedMetadata).length === 0);
|
|
4176
|
+
}
|
|
4177
|
+
resolveMaterializedReadUrl() {
|
|
4178
|
+
const materialization = this.context?.['materialization'];
|
|
4179
|
+
return String(materialization?.['readUrl'] || '').trim();
|
|
4180
|
+
}
|
|
4181
|
+
unwrapRestData(value) {
|
|
4182
|
+
if (value && typeof value === 'object' && !Array.isArray(value) && 'data' in value) {
|
|
4183
|
+
return value.data;
|
|
4184
|
+
}
|
|
4185
|
+
return value;
|
|
4186
|
+
}
|
|
4187
|
+
extractCollectionData(data) {
|
|
4188
|
+
if (Array.isArray(data)) {
|
|
4189
|
+
return data;
|
|
4190
|
+
}
|
|
4191
|
+
if (!data || typeof data !== 'object') {
|
|
4192
|
+
return null;
|
|
4193
|
+
}
|
|
4194
|
+
const record = data;
|
|
4195
|
+
if (Array.isArray(record['content'])) {
|
|
4196
|
+
return record['content'];
|
|
4197
|
+
}
|
|
4198
|
+
if (Array.isArray(record['items'])) {
|
|
4199
|
+
return record['items'];
|
|
4200
|
+
}
|
|
4201
|
+
if (Array.isArray(record['data'])) {
|
|
4202
|
+
return record['data'];
|
|
4203
|
+
}
|
|
4204
|
+
return null;
|
|
4205
|
+
}
|
|
3847
4206
|
emitTableRuntimeConfigSnapshot() {
|
|
3848
4207
|
const snapshot = this.getCurrentTableConfigSnapshot();
|
|
3849
4208
|
if (!snapshot)
|
|
@@ -3901,11 +4260,47 @@ class PraxisCrudComponent {
|
|
|
3901
4260
|
}
|
|
3902
4261
|
}
|
|
3903
4262
|
resolveQueryContext(meta) {
|
|
4263
|
+
if (Array.isArray(meta?.data) && this.resolveResourcePath(meta).length === 0) {
|
|
4264
|
+
return null;
|
|
4265
|
+
}
|
|
3904
4266
|
return this.isRecord(meta?.queryContext) ? meta.queryContext : null;
|
|
3905
4267
|
}
|
|
3906
4268
|
resolveFilterCriteria(meta) {
|
|
3907
4269
|
return this.isRecord(meta?.filterCriteria) ? { ...meta.filterCriteria } : {};
|
|
3908
4270
|
}
|
|
4271
|
+
extractRowFromEvent(event) {
|
|
4272
|
+
if (!this.isRecord(event)) {
|
|
4273
|
+
return null;
|
|
4274
|
+
}
|
|
4275
|
+
const row = event['row'];
|
|
4276
|
+
return this.isRecord(row) ? row : null;
|
|
4277
|
+
}
|
|
4278
|
+
extractSelectedRowFromEvent(event) {
|
|
4279
|
+
if (!this.isRecord(event)) {
|
|
4280
|
+
return null;
|
|
4281
|
+
}
|
|
4282
|
+
const selection = event['selection'];
|
|
4283
|
+
if (Array.isArray(selection)) {
|
|
4284
|
+
const first = selection[0];
|
|
4285
|
+
return this.isRecord(first) ? first : null;
|
|
4286
|
+
}
|
|
4287
|
+
const row = event['row'];
|
|
4288
|
+
if (this.isRecord(row)) {
|
|
4289
|
+
return row;
|
|
4290
|
+
}
|
|
4291
|
+
return null;
|
|
4292
|
+
}
|
|
4293
|
+
extractSelectedRowFromBulkAction(event) {
|
|
4294
|
+
const first = Array.isArray(event?.rows) ? event.rows[0] : null;
|
|
4295
|
+
return this.isRecord(first) ? first : null;
|
|
4296
|
+
}
|
|
4297
|
+
resolveSelectedRowForAction(action) {
|
|
4298
|
+
const normalized = String(action || '').trim().toLowerCase();
|
|
4299
|
+
if (!normalized || normalized === 'create' || normalized === 'add' || normalized === 'new') {
|
|
4300
|
+
return undefined;
|
|
4301
|
+
}
|
|
4302
|
+
return this.selectedRow ?? undefined;
|
|
4303
|
+
}
|
|
3909
4304
|
async tryOpenDiscoveredCrudSurface(action, row, runtimeEvent) {
|
|
3910
4305
|
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
3911
4306
|
if (!this.surfaceService) {
|
|
@@ -3942,7 +4337,7 @@ class PraxisCrudComponent {
|
|
|
3942
4337
|
}
|
|
3943
4338
|
let openPromise;
|
|
3944
4339
|
try {
|
|
3945
|
-
|
|
4340
|
+
const surfaceContext = {
|
|
3946
4341
|
sourceId: this.componentKeyId() || undefined,
|
|
3947
4342
|
payload: { action: normalizedAction, row: row ?? null },
|
|
3948
4343
|
meta: {
|
|
@@ -3959,7 +4354,9 @@ class PraxisCrudComponent {
|
|
|
3959
4354
|
resourcePath,
|
|
3960
4355
|
},
|
|
3961
4356
|
},
|
|
3962
|
-
}
|
|
4357
|
+
};
|
|
4358
|
+
const handledInline = await this.surfaceOutlets.tryActivate(payload, surfaceContext);
|
|
4359
|
+
openPromise = handledInline ? Promise.resolve(undefined) : Promise.resolve(this.surfaceService.open(payload, surfaceContext));
|
|
3963
4360
|
}
|
|
3964
4361
|
catch {
|
|
3965
4362
|
return false;
|
|
@@ -3999,7 +4396,7 @@ class PraxisCrudComponent {
|
|
|
3999
4396
|
const providedAction = this.resolveProvidedWorkflowAction(normalizedAction, runtimeEvent?.actionConfig);
|
|
4000
4397
|
const catalog = providedAction ? null : await this.resolveDiscoveredActionCatalog(row);
|
|
4001
4398
|
const discoveredAction = providedAction || this.selectDiscoveredWorkflowAction(normalizedAction, catalog?.actions || []);
|
|
4002
|
-
const resourcePath = String(this.resolveResourcePath(this.resolvedMetadata) ||
|
|
4399
|
+
const resourcePath = String(catalog?.resourcePath || this.resolveResourcePath(this.resolvedMetadata) || '').trim();
|
|
4003
4400
|
if (!discoveredAction || !resourcePath) {
|
|
4004
4401
|
return false;
|
|
4005
4402
|
}
|
|
@@ -4222,12 +4619,19 @@ class PraxisCrudComponent {
|
|
|
4222
4619
|
}
|
|
4223
4620
|
return cfg.toolbar;
|
|
4224
4621
|
};
|
|
4225
|
-
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.
|
|
4622
|
+
const hasToolbarAdd = (cfg.toolbar?.actions || []).some((action) => this.shouldCanonicalizeToolbarCreateAction(action));
|
|
4226
4623
|
const addAction = this.resolveCreateToolbarAction(meta, capabilities);
|
|
4227
4624
|
if (addAction) {
|
|
4228
4625
|
if (hasToolbarAdd) {
|
|
4229
4626
|
const toolbar = ensureToolbar();
|
|
4230
4627
|
toolbar.visible = true;
|
|
4628
|
+
toolbar.actions = (toolbar.actions || []).map((action) => this.shouldCanonicalizeToolbarCreateAction(action)
|
|
4629
|
+
? {
|
|
4630
|
+
...action,
|
|
4631
|
+
action: 'create',
|
|
4632
|
+
}
|
|
4633
|
+
: action);
|
|
4634
|
+
changed = true;
|
|
4231
4635
|
}
|
|
4232
4636
|
else {
|
|
4233
4637
|
const toolbar = ensureToolbar();
|
|
@@ -4246,6 +4650,7 @@ class PraxisCrudComponent {
|
|
|
4246
4650
|
});
|
|
4247
4651
|
}
|
|
4248
4652
|
changed = true;
|
|
4653
|
+
changed = this.ensureCanonicalCollectionEmptyState(cfg, meta, addAction) || changed;
|
|
4249
4654
|
}
|
|
4250
4655
|
const discoveredToolbarActions = this.resolveCollectionWorkflowToolbarActions(capabilities);
|
|
4251
4656
|
if (discoveredToolbarActions.length) {
|
|
@@ -4362,6 +4767,28 @@ class PraxisCrudComponent {
|
|
|
4362
4767
|
}
|
|
4363
4768
|
return false;
|
|
4364
4769
|
}
|
|
4770
|
+
normalizeCrudActionName(action) {
|
|
4771
|
+
const raw = String(action || '').trim();
|
|
4772
|
+
const normalized = raw.toLowerCase();
|
|
4773
|
+
return this.isCreateActionAliasName(normalized) ? 'create' : raw;
|
|
4774
|
+
}
|
|
4775
|
+
shouldCanonicalizeToolbarCreateAction(action) {
|
|
4776
|
+
if (!action)
|
|
4777
|
+
return false;
|
|
4778
|
+
const normalize = (value) => String(value || '').trim().toLowerCase();
|
|
4779
|
+
const explicitId = normalize(action.action || action.id || action.code || action.key || action.name || action.type);
|
|
4780
|
+
if (this.isCreateActionAliasName(explicitId))
|
|
4781
|
+
return true;
|
|
4782
|
+
if (explicitId)
|
|
4783
|
+
return false;
|
|
4784
|
+
const icon = normalize(action.icon);
|
|
4785
|
+
const label = normalize(action.label);
|
|
4786
|
+
return ((icon === 'add' || icon === 'add_circle' || icon === 'add_box') &&
|
|
4787
|
+
(label === 'adicionar' || label === 'novo' || label === 'criar' || label === 'incluir'));
|
|
4788
|
+
}
|
|
4789
|
+
isCreateActionAliasName(actionName) {
|
|
4790
|
+
return ['create', 'add', 'novo', 'new', 'incluir', 'inserir'].includes(actionName);
|
|
4791
|
+
}
|
|
4365
4792
|
buildTableCrudContext(meta, capabilities) {
|
|
4366
4793
|
if (!meta)
|
|
4367
4794
|
return undefined;
|
|
@@ -4384,6 +4811,9 @@ class PraxisCrudComponent {
|
|
|
4384
4811
|
};
|
|
4385
4812
|
}
|
|
4386
4813
|
openCrudAuthoringFromTable = () => {
|
|
4814
|
+
if (!this.isCustomizationAvailable()) {
|
|
4815
|
+
return;
|
|
4816
|
+
}
|
|
4387
4817
|
const seed = this.currentAuthoringDocument
|
|
4388
4818
|
|| createCrudAuthoringDocument({ metadata: this.resolvedMetadata });
|
|
4389
4819
|
const ref = openCrudMetadataEditor(this.settingsPanel, {
|
|
@@ -4392,13 +4822,33 @@ class PraxisCrudComponent {
|
|
|
4392
4822
|
title: this.tx('crud.authoring.title', 'Configurações do CRUD'),
|
|
4393
4823
|
titleIcon: 'table_chart',
|
|
4394
4824
|
});
|
|
4825
|
+
this.activeAuthoringPanelRef = ref;
|
|
4395
4826
|
ref.applied$
|
|
4396
4827
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4397
4828
|
.subscribe((payload) => this.applyCrudAuthoringPayload(payload, 'applied'));
|
|
4398
4829
|
ref.saved$
|
|
4399
4830
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4400
4831
|
.subscribe((payload) => this.applyCrudAuthoringPayload(payload, 'saved'));
|
|
4832
|
+
ref.closed$
|
|
4833
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4834
|
+
.subscribe(() => {
|
|
4835
|
+
if (this.activeAuthoringPanelRef === ref) {
|
|
4836
|
+
this.activeAuthoringPanelRef = undefined;
|
|
4837
|
+
}
|
|
4838
|
+
});
|
|
4401
4839
|
};
|
|
4840
|
+
isCustomizationAvailable() {
|
|
4841
|
+
if (!this.enableCustomization) {
|
|
4842
|
+
return false;
|
|
4843
|
+
}
|
|
4844
|
+
const requiredCapability = String(this.authoringCapability || '').trim();
|
|
4845
|
+
if (!requiredCapability) {
|
|
4846
|
+
return true;
|
|
4847
|
+
}
|
|
4848
|
+
const capabilities = this.enterpriseRuntimeContext?.snapshot?.capabilities;
|
|
4849
|
+
return Array.isArray(capabilities)
|
|
4850
|
+
&& capabilities.some((capability) => capability === requiredCapability);
|
|
4851
|
+
}
|
|
4402
4852
|
applyCrudAuthoringPayload(payload, eventName) {
|
|
4403
4853
|
const next = parseLegacyOrCrudDocument(payload);
|
|
4404
4854
|
this.currentAuthoringDocument = next;
|
|
@@ -4615,6 +5065,48 @@ class PraxisCrudComponent {
|
|
|
4615
5065
|
return action;
|
|
4616
5066
|
}
|
|
4617
5067
|
}
|
|
5068
|
+
ensureCanonicalCollectionEmptyState(config, metadata, createAction) {
|
|
5069
|
+
const behavior = config.behavior || {};
|
|
5070
|
+
if (behavior.emptyState) {
|
|
5071
|
+
return false;
|
|
5072
|
+
}
|
|
5073
|
+
const resourceLabel = String(metadata.resource?.label
|
|
5074
|
+
|| metadata.resource?.title
|
|
5075
|
+
|| metadata.resource?.path
|
|
5076
|
+
|| '').trim();
|
|
5077
|
+
const title = resourceLabel
|
|
5078
|
+
? this.txWithParams('crud.table.emptyState.initial.title', 'Sem registros em {label}', { label: resourceLabel })
|
|
5079
|
+
: this.tx('crud.table.emptyState.initial.titleFallback', 'Nenhum registro disponível.');
|
|
5080
|
+
const actionId = String(createAction.action || createAction.id || 'create').trim() || 'create';
|
|
5081
|
+
const actionLabel = String(createAction.label || this.getCrudActionLabel('create')).trim();
|
|
5082
|
+
const actionIcon = String(createAction.icon || 'add').trim();
|
|
5083
|
+
config.behavior = {
|
|
5084
|
+
...behavior,
|
|
5085
|
+
emptyState: {
|
|
5086
|
+
message: '',
|
|
5087
|
+
contexts: {
|
|
5088
|
+
initial: {
|
|
5089
|
+
title,
|
|
5090
|
+
description: this.tx('crud.table.emptyState.initial.descriptionWithAction', 'Use a ação principal para adicionar o primeiro registro quando houver informações para cadastrar.'),
|
|
5091
|
+
actions: [
|
|
5092
|
+
{
|
|
5093
|
+
label: actionLabel,
|
|
5094
|
+
action: actionId,
|
|
5095
|
+
icon: actionIcon,
|
|
5096
|
+
primary: true,
|
|
5097
|
+
},
|
|
5098
|
+
],
|
|
5099
|
+
},
|
|
5100
|
+
filtered: {
|
|
5101
|
+
title: this.tx('crud.table.emptyState.filtered.title', 'Nenhum resultado encontrado.'),
|
|
5102
|
+
description: this.tx('crud.table.emptyState.filtered.description', 'Revise os filtros ou ajuste o termo de busca.'),
|
|
5103
|
+
actions: [],
|
|
5104
|
+
},
|
|
5105
|
+
},
|
|
5106
|
+
},
|
|
5107
|
+
};
|
|
5108
|
+
return true;
|
|
5109
|
+
}
|
|
4618
5110
|
tx(key, fallback) {
|
|
4619
5111
|
try {
|
|
4620
5112
|
return translateCrudRuntimeText(this.i18n, key, fallback);
|
|
@@ -4623,8 +5115,19 @@ class PraxisCrudComponent {
|
|
|
4623
5115
|
return fallback;
|
|
4624
5116
|
}
|
|
4625
5117
|
}
|
|
5118
|
+
txWithParams(key, fallback, params) {
|
|
5119
|
+
try {
|
|
5120
|
+
return this.interpolateTranslationParams(translateCrudRuntimeText(this.i18n, key, fallback, params), params);
|
|
5121
|
+
}
|
|
5122
|
+
catch {
|
|
5123
|
+
return this.interpolateTranslationParams(fallback, params);
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
interpolateTranslationParams(text, params) {
|
|
5127
|
+
return Object.entries(params).reduce((current, [name, value]) => current.replaceAll(`{${name}}`, value), text);
|
|
5128
|
+
}
|
|
4626
5129
|
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: [
|
|
5130
|
+
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
5131
|
providePraxisI18nConfig(RESOURCE_DISCOVERY_I18N_CONFIG),
|
|
4629
5132
|
providePraxisI18nConfig(PRAXIS_CRUD_RUNTIME_I18N_CONFIG),
|
|
4630
5133
|
], viewQueries: [{ propertyName: "table", first: true, predicate: PraxisTable, descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
@@ -4638,17 +5141,19 @@ class PraxisCrudComponent {
|
|
|
4638
5141
|
[tableId]="crudId || 'default'"
|
|
4639
5142
|
[crudContext]="tableCrudContext"
|
|
4640
5143
|
[enableCustomization]="enableCustomization"
|
|
5144
|
+
[authoringCapability]="authoringCapability"
|
|
4641
5145
|
(rowClick)="onTableRowClick($event)"
|
|
4642
5146
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4643
5147
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4644
5148
|
(toolbarAction)="onAction($event.action)"
|
|
5149
|
+
(bulkAction)="onBulkAction($event)"
|
|
4645
5150
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4646
5151
|
(reset)="onResetPreferences()"
|
|
4647
5152
|
(metadataChange)="onTableMetadataChange()"
|
|
4648
5153
|
(loadingStateChange)="onTableLoadingStateChange($event)"
|
|
4649
5154
|
></praxis-table>
|
|
4650
5155
|
} @else {
|
|
4651
|
-
@if (
|
|
5156
|
+
@if (isCustomizationAvailable()) {
|
|
4652
5157
|
<praxis-empty-state-card
|
|
4653
5158
|
icon="table_rows"
|
|
4654
5159
|
[title]="getEmptyStateTitle()"
|
|
@@ -4657,7 +5162,7 @@ class PraxisCrudComponent {
|
|
|
4657
5162
|
/>
|
|
4658
5163
|
}
|
|
4659
5164
|
}
|
|
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"] }] });
|
|
5165
|
+
`, 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
5166
|
}
|
|
4662
5167
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisCrudComponent, decorators: [{
|
|
4663
5168
|
type: Component,
|
|
@@ -4675,17 +5180,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4675
5180
|
[tableId]="crudId || 'default'"
|
|
4676
5181
|
[crudContext]="tableCrudContext"
|
|
4677
5182
|
[enableCustomization]="enableCustomization"
|
|
5183
|
+
[authoringCapability]="authoringCapability"
|
|
4678
5184
|
(rowClick)="onTableRowClick($event)"
|
|
4679
5185
|
(selectionChange)="onTableSelectionChange($event)"
|
|
4680
5186
|
(rowAction)="onAction($event.action, $event.row, $event)"
|
|
4681
5187
|
(toolbarAction)="onAction($event.action)"
|
|
5188
|
+
(bulkAction)="onBulkAction($event)"
|
|
4682
5189
|
(collectionLinksChange)="onCollectionLinksChange($event)"
|
|
4683
5190
|
(reset)="onResetPreferences()"
|
|
4684
5191
|
(metadataChange)="onTableMetadataChange()"
|
|
4685
5192
|
(loadingStateChange)="onTableLoadingStateChange($event)"
|
|
4686
5193
|
></praxis-table>
|
|
4687
5194
|
} @else {
|
|
4688
|
-
@if (
|
|
5195
|
+
@if (isCustomizationAvailable()) {
|
|
4689
5196
|
<praxis-empty-state-card
|
|
4690
5197
|
icon="table_rows"
|
|
4691
5198
|
[title]="getEmptyStateTitle()"
|
|
@@ -4695,7 +5202,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4695
5202
|
}
|
|
4696
5203
|
}
|
|
4697
5204
|
`, styles: [":host{display:block;width:100%;min-width:0;max-width:100%}\n"] }]
|
|
4698
|
-
}], propDecorators: { metadata: [{
|
|
5205
|
+
}], ctorParameters: () => [], propDecorators: { metadata: [{
|
|
4699
5206
|
type: Input,
|
|
4700
5207
|
args: [{ required: true }]
|
|
4701
5208
|
}], crudId: [{
|
|
@@ -4707,6 +5214,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4707
5214
|
type: Input
|
|
4708
5215
|
}], enableCustomization: [{
|
|
4709
5216
|
type: Input
|
|
5217
|
+
}], authoringCapability: [{
|
|
5218
|
+
type: Input
|
|
4710
5219
|
}], configureRequested: [{
|
|
4711
5220
|
type: Output
|
|
4712
5221
|
}], afterOpen: [{
|
|
@@ -4734,6 +5243,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
4734
5243
|
args: [PraxisTable]
|
|
4735
5244
|
}] } });
|
|
4736
5245
|
|
|
5246
|
+
function isCrudDebugEnabled() {
|
|
5247
|
+
try {
|
|
5248
|
+
if (globalThis.__PRAXIS_DEBUG_CRUD__) {
|
|
5249
|
+
return true;
|
|
5250
|
+
}
|
|
5251
|
+
if (typeof localStorage !== 'undefined') {
|
|
5252
|
+
return localStorage.getItem('praxis.debug.crud') === 'true';
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
catch { }
|
|
5256
|
+
return false;
|
|
5257
|
+
}
|
|
5258
|
+
function debugCrudHost(message, data) {
|
|
5259
|
+
if (!isCrudDebugEnabled()) {
|
|
5260
|
+
return;
|
|
5261
|
+
}
|
|
5262
|
+
try {
|
|
5263
|
+
console.debug(message, data);
|
|
5264
|
+
}
|
|
5265
|
+
catch { }
|
|
5266
|
+
}
|
|
4737
5267
|
class DynamicFormDialogHostComponent {
|
|
4738
5268
|
dialogRef;
|
|
4739
5269
|
data;
|
|
@@ -4742,8 +5272,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4742
5272
|
configStorage;
|
|
4743
5273
|
formComp;
|
|
4744
5274
|
modal = {};
|
|
5275
|
+
presentation = 'modal';
|
|
4745
5276
|
maximized = false;
|
|
4746
5277
|
initialSize = {};
|
|
5278
|
+
initialPosition;
|
|
4747
5279
|
rememberState = false;
|
|
4748
5280
|
stateKey;
|
|
4749
5281
|
backDefaults = {};
|
|
@@ -4751,12 +5283,16 @@ class DynamicFormDialogHostComponent {
|
|
|
4751
5283
|
resourcePath;
|
|
4752
5284
|
resourceId;
|
|
4753
5285
|
initialValue;
|
|
5286
|
+
resourceIdentity = null;
|
|
4754
5287
|
schemaUrl;
|
|
4755
5288
|
submitUrl;
|
|
4756
5289
|
submitMethod;
|
|
4757
5290
|
apiEndpointKey;
|
|
4758
5291
|
apiUrlEntry;
|
|
5292
|
+
layoutPolicy;
|
|
5293
|
+
formConfig = {};
|
|
4759
5294
|
formActions;
|
|
5295
|
+
formConfigPersistenceStrategy = 'input-first';
|
|
4760
5296
|
mode = 'create';
|
|
4761
5297
|
backConfig;
|
|
4762
5298
|
idField = 'id';
|
|
@@ -4778,6 +5314,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4778
5314
|
this.crud = crud;
|
|
4779
5315
|
this.configStorage = configStorage;
|
|
4780
5316
|
this.dialogRef.disableClose = true;
|
|
5317
|
+
this.presentation = this.data.presentation === 'drawer' ? 'drawer' : 'modal';
|
|
4781
5318
|
// i18n
|
|
4782
5319
|
this.texts = {
|
|
4783
5320
|
...this.texts,
|
|
@@ -4788,6 +5325,9 @@ class DynamicFormDialogHostComponent {
|
|
|
4788
5325
|
canMaximize: true,
|
|
4789
5326
|
...(this.data.metadata?.defaults?.modal || {}),
|
|
4790
5327
|
};
|
|
5328
|
+
this.initialPosition = this.presentation === 'drawer'
|
|
5329
|
+
? (this.data.dialogPosition ?? this.modal.position ?? { right: '0', top: '0' })
|
|
5330
|
+
: undefined;
|
|
4791
5331
|
this.rememberState = !!this.modal.rememberLastState;
|
|
4792
5332
|
this.stateKey = this.data.action?.formId
|
|
4793
5333
|
? `crud-dialog-state:${this.data.action.formId}`
|
|
@@ -4812,12 +5352,17 @@ class DynamicFormDialogHostComponent {
|
|
|
4812
5352
|
this.apiUrlEntry = this.data.inputs?.['apiUrlEntry'] ?? null;
|
|
4813
5353
|
const act = this.data.action?.action;
|
|
4814
5354
|
this.mode = act === 'edit' ? 'edit' : act === 'view' ? 'view' : 'create';
|
|
5355
|
+
this.resourceIdentity = this.presentation === 'drawer' && this.mode === 'edit' && this.data.resourceIdentity
|
|
5356
|
+
? this.data.resourceIdentity
|
|
5357
|
+
: null;
|
|
5358
|
+
this.formConfig = this.resolveFormConfig();
|
|
5359
|
+
this.layoutPolicy = this.resolveLayoutPolicy();
|
|
4815
5360
|
this.formActions = this.resolveFormActions();
|
|
4816
5361
|
// Back config: defaults from metadata/action, overridden by saved per-form config
|
|
4817
5362
|
const defaults = (this.data.action?.back || this.data.metadata?.defaults?.back) || {};
|
|
4818
5363
|
this.backDefaults = defaults;
|
|
4819
5364
|
this.backConfig = { ...defaults };
|
|
4820
|
-
|
|
5365
|
+
debugCrudHost('[CRUD:Host] constructed', {
|
|
4821
5366
|
action: this.data?.action,
|
|
4822
5367
|
resourcePath: this.resourcePath,
|
|
4823
5368
|
resourceId: this.resourceId,
|
|
@@ -4857,6 +5402,7 @@ class DynamicFormDialogHostComponent {
|
|
|
4857
5402
|
'apiEndpointKey',
|
|
4858
5403
|
'apiUrlEntry',
|
|
4859
5404
|
'initialValue',
|
|
5405
|
+
'layoutPolicy',
|
|
4860
5406
|
]);
|
|
4861
5407
|
const explicit = inputs['initialValue'] && typeof inputs['initialValue'] === 'object'
|
|
4862
5408
|
? { ...inputs['initialValue'] }
|
|
@@ -4869,10 +5415,50 @@ class DynamicFormDialogHostComponent {
|
|
|
4869
5415
|
}
|
|
4870
5416
|
return Object.keys(explicit).length ? explicit : null;
|
|
4871
5417
|
}
|
|
5418
|
+
resolveFormConfig() {
|
|
5419
|
+
const config = this.data.metadata?.form;
|
|
5420
|
+
return config && typeof config === 'object' ? config : {};
|
|
5421
|
+
}
|
|
5422
|
+
get dialogTitle() {
|
|
5423
|
+
const actionLabel = this.resolveActionDialogTitle();
|
|
5424
|
+
return actionLabel ||
|
|
5425
|
+
stringOrUndefined(this.formConfig['title']) ||
|
|
5426
|
+
deriveModeTitle(this.mode, this.resolveResourceTitle()) ||
|
|
5427
|
+
this.texts.title;
|
|
5428
|
+
}
|
|
5429
|
+
resolveActionDialogTitle() {
|
|
5430
|
+
const actionLabel = stringOrUndefined(this.data.action?.label);
|
|
5431
|
+
if (!actionLabel) {
|
|
5432
|
+
return undefined;
|
|
5433
|
+
}
|
|
5434
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5435
|
+
const table = this.data.metadata?.table ?? {};
|
|
5436
|
+
const businessTitle = stringOrUndefined(resource.formTitle ??
|
|
5437
|
+
resource.title ??
|
|
5438
|
+
resource.label ??
|
|
5439
|
+
table.title ??
|
|
5440
|
+
table.label);
|
|
5441
|
+
const routeFallbackTitle = deriveModeTitle(this.mode, titleFromResourcePath(this.resourcePath));
|
|
5442
|
+
return businessTitle && routeFallbackTitle && sameTitle(actionLabel, routeFallbackTitle)
|
|
5443
|
+
? undefined
|
|
5444
|
+
: actionLabel;
|
|
5445
|
+
}
|
|
5446
|
+
resolveResourceTitle() {
|
|
5447
|
+
const resource = this.data.metadata?.resource ?? {};
|
|
5448
|
+
const table = this.data.metadata?.table ?? {};
|
|
5449
|
+
return stringOrUndefined(resource.formTitle ??
|
|
5450
|
+
resource.title ??
|
|
5451
|
+
resource.label ??
|
|
5452
|
+
table.title ??
|
|
5453
|
+
table.label) || titleFromResourcePath(this.resourcePath);
|
|
5454
|
+
}
|
|
4872
5455
|
resolveFormActions() {
|
|
4873
5456
|
if (this.mode === 'view') {
|
|
4874
5457
|
return undefined;
|
|
4875
5458
|
}
|
|
5459
|
+
if (this.formConfig?.actions) {
|
|
5460
|
+
return undefined;
|
|
5461
|
+
}
|
|
4876
5462
|
const submitLabel = this.resolveSubmitLabel();
|
|
4877
5463
|
if (!submitLabel) {
|
|
4878
5464
|
return undefined;
|
|
@@ -4890,6 +5476,15 @@ class DynamicFormDialogHostComponent {
|
|
|
4890
5476
|
},
|
|
4891
5477
|
};
|
|
4892
5478
|
}
|
|
5479
|
+
resolveLayoutPolicy() {
|
|
5480
|
+
const explicit = this.data.inputs?.['layoutPolicy'] ??
|
|
5481
|
+
this.data.action?.form?.layoutPolicy ??
|
|
5482
|
+
this.data.action?.layoutPolicy;
|
|
5483
|
+
if (explicit && typeof explicit === 'object') {
|
|
5484
|
+
return explicit;
|
|
5485
|
+
}
|
|
5486
|
+
return null;
|
|
5487
|
+
}
|
|
4893
5488
|
resolveSubmitLabel() {
|
|
4894
5489
|
const action = this.data.action ?? {};
|
|
4895
5490
|
const explicit = stringOrUndefined(action.form?.submitLabel ??
|
|
@@ -4962,10 +5557,10 @@ class DynamicFormDialogHostComponent {
|
|
|
4962
5557
|
ref
|
|
4963
5558
|
.afterClosed()
|
|
4964
5559
|
.pipe(filter((confirmed) => !!confirmed), takeUntilDestroyed(this.destroyRef))
|
|
4965
|
-
.subscribe(() => this.dialogRef.close());
|
|
5560
|
+
.subscribe(() => this.dialogRef.close({ type: 'close' }));
|
|
4966
5561
|
}
|
|
4967
5562
|
else {
|
|
4968
|
-
this.dialogRef.close();
|
|
5563
|
+
this.dialogRef.close({ type: 'close' });
|
|
4969
5564
|
}
|
|
4970
5565
|
}
|
|
4971
5566
|
toggleMaximize(initial = false) {
|
|
@@ -4978,11 +5573,22 @@ class DynamicFormDialogHostComponent {
|
|
|
4978
5573
|
? `calc(100dvh - ${2 * (gap ?? 0)}px)`
|
|
4979
5574
|
: this.initialSize.height;
|
|
4980
5575
|
this.dialogRef.updateSize(width, height);
|
|
4981
|
-
this.
|
|
5576
|
+
this.updateHostPosition();
|
|
4982
5577
|
const pane = this.dialogRef
|
|
4983
5578
|
?._containerInstance?._elementRef?.nativeElement?.parentElement;
|
|
4984
5579
|
if (pane && pane.classList.contains('pfx-dialog-pane')) {
|
|
4985
|
-
|
|
5580
|
+
if (this.presentation === 'drawer') {
|
|
5581
|
+
pane.classList.toggle('pfx-drawer-maximized', this.maximized);
|
|
5582
|
+
if (this.maximized) {
|
|
5583
|
+
pane.style.setProperty('--pfx-drawer-edge-gap', `${gap ?? 0}px`);
|
|
5584
|
+
}
|
|
5585
|
+
else {
|
|
5586
|
+
pane.style.removeProperty('--pfx-drawer-edge-gap');
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
else {
|
|
5590
|
+
pane.style.margin = this.maximized ? `${gap}px` : '';
|
|
5591
|
+
}
|
|
4986
5592
|
}
|
|
4987
5593
|
this.saveState();
|
|
4988
5594
|
}
|
|
@@ -5008,7 +5614,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5008
5614
|
width: saved?.width ?? this.modal.width,
|
|
5009
5615
|
height: saved?.height ?? this.modal.height,
|
|
5010
5616
|
};
|
|
5011
|
-
|
|
5617
|
+
debugCrudHost('[CRUD:Host] ngOnInit', {
|
|
5012
5618
|
initialSize: this.initialSize,
|
|
5013
5619
|
startMaximized: this.modal.startMaximized,
|
|
5014
5620
|
fullscreenBreakpoint: this.modal.fullscreenBreakpoint,
|
|
@@ -5028,14 +5634,34 @@ class DynamicFormDialogHostComponent {
|
|
|
5028
5634
|
}
|
|
5029
5635
|
else if (this.initialSize.width || this.initialSize.height) {
|
|
5030
5636
|
this.dialogRef.updateSize(this.initialSize.width, this.initialSize.height);
|
|
5637
|
+
this.updateHostPosition();
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
updateHostPosition() {
|
|
5641
|
+
if (this.presentation !== 'drawer') {
|
|
5031
5642
|
this.dialogRef.updatePosition();
|
|
5643
|
+
return;
|
|
5032
5644
|
}
|
|
5645
|
+
this.dialogRef.updatePosition(this.maximized
|
|
5646
|
+
? this.resolveMaximizedDrawerPosition()
|
|
5647
|
+
: this.initialPosition);
|
|
5648
|
+
}
|
|
5649
|
+
resolveMaximizedDrawerPosition() {
|
|
5650
|
+
const position = this.initialPosition ?? { right: '0', top: '0' };
|
|
5651
|
+
const offset = `${this.modal.edgeGap ?? 8}px`;
|
|
5652
|
+
const horizontal = position.left != null
|
|
5653
|
+
? { left: offset }
|
|
5654
|
+
: { right: offset };
|
|
5655
|
+
const vertical = position.bottom != null && position.top == null
|
|
5656
|
+
? { bottom: offset }
|
|
5657
|
+
: { top: offset };
|
|
5658
|
+
return { ...horizontal, ...vertical };
|
|
5033
5659
|
}
|
|
5034
5660
|
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\"" }, classAttribute: "praxis-dialog" }, providers: [GenericCrudService], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
|
|
5661
|
+
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: [GenericCrudService], viewQueries: [{ propertyName: "formComp", first: true, predicate: PraxisDynamicForm, descendants: true }], ngImport: i0, template: `
|
|
5036
5662
|
<div mat-dialog-title class="dialog-header">
|
|
5037
5663
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5038
|
-
{{
|
|
5664
|
+
{{ dialogTitle }}
|
|
5039
5665
|
</h2>
|
|
5040
5666
|
<span class="spacer"></span>
|
|
5041
5667
|
@if (modal.canMaximize) {
|
|
@@ -5063,17 +5689,25 @@ class DynamicFormDialogHostComponent {
|
|
|
5063
5689
|
class="dialog-content"
|
|
5064
5690
|
aria-labelledby="crudDialogTitle"
|
|
5065
5691
|
>
|
|
5692
|
+
@if (resourceIdentity) {
|
|
5693
|
+
<section class="crud-drawer-resource-identity" data-testid="crud-drawer-resource-identity">
|
|
5694
|
+
<praxis-resource-identity [identity]="resourceIdentity"></praxis-resource-identity>
|
|
5695
|
+
</section>
|
|
5696
|
+
}
|
|
5066
5697
|
<praxis-dynamic-form
|
|
5067
5698
|
[formId]="data.action?.formId"
|
|
5068
5699
|
[resourcePath]="resourcePath"
|
|
5069
5700
|
[resourceId]="resourceId"
|
|
5070
5701
|
[initialValue]="initialValue"
|
|
5071
5702
|
[mode]="mode"
|
|
5703
|
+
[config]="formConfig"
|
|
5072
5704
|
[schemaUrl]="schemaUrl"
|
|
5073
5705
|
[submitUrl]="submitUrl"
|
|
5074
5706
|
[submitMethod]="submitMethod"
|
|
5075
5707
|
[apiEndpointKey]="apiEndpointKey"
|
|
5076
5708
|
[apiUrlEntry]="apiUrlEntry"
|
|
5709
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5710
|
+
[layoutPolicy]="layoutPolicy"
|
|
5077
5711
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5078
5712
|
[backConfig]="backConfig"
|
|
5079
5713
|
[actions]="formActions"
|
|
@@ -5081,7 +5715,7 @@ class DynamicFormDialogHostComponent {
|
|
|
5081
5715
|
(formCancel)="onCancel()"
|
|
5082
5716
|
></praxis-dynamic-form>
|
|
5083
5717
|
</mat-dialog-content>
|
|
5084
|
-
`, isInline: true, styles: ["
|
|
5718
|
+
`, 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.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.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}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: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i6.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisResourceIdentityComponent, selector: "praxis-resource-identity", inputs: ["identity", "emptyTitle", "density", "showMetadataLabels"] }, { 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
5719
|
}
|
|
5086
5720
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DynamicFormDialogHostComponent, decorators: [{
|
|
5087
5721
|
type: Component,
|
|
@@ -5090,14 +5724,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5090
5724
|
MatButtonModule,
|
|
5091
5725
|
MatIconModule,
|
|
5092
5726
|
PraxisIconDirective,
|
|
5727
|
+
PraxisResourceIdentityComponent,
|
|
5093
5728
|
PraxisDynamicForm
|
|
5094
|
-
], providers: [GenericCrudService], host: {
|
|
5729
|
+
], encapsulation: ViewEncapsulation.None, providers: [GenericCrudService], host: {
|
|
5095
5730
|
class: 'praxis-dialog',
|
|
5096
5731
|
'[attr.data-density]': 'modal.density || "default"',
|
|
5732
|
+
'[attr.data-presentation]': 'presentation',
|
|
5733
|
+
'[class.praxis-drawer]': 'presentation === "drawer"',
|
|
5097
5734
|
}, template: `
|
|
5098
5735
|
<div mat-dialog-title class="dialog-header">
|
|
5099
5736
|
<h2 id="crudDialogTitle" class="dialog-title">
|
|
5100
|
-
{{
|
|
5737
|
+
{{ dialogTitle }}
|
|
5101
5738
|
</h2>
|
|
5102
5739
|
<span class="spacer"></span>
|
|
5103
5740
|
@if (modal.canMaximize) {
|
|
@@ -5125,17 +5762,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5125
5762
|
class="dialog-content"
|
|
5126
5763
|
aria-labelledby="crudDialogTitle"
|
|
5127
5764
|
>
|
|
5765
|
+
@if (resourceIdentity) {
|
|
5766
|
+
<section class="crud-drawer-resource-identity" data-testid="crud-drawer-resource-identity">
|
|
5767
|
+
<praxis-resource-identity [identity]="resourceIdentity"></praxis-resource-identity>
|
|
5768
|
+
</section>
|
|
5769
|
+
}
|
|
5128
5770
|
<praxis-dynamic-form
|
|
5129
5771
|
[formId]="data.action?.formId"
|
|
5130
5772
|
[resourcePath]="resourcePath"
|
|
5131
5773
|
[resourceId]="resourceId"
|
|
5132
5774
|
[initialValue]="initialValue"
|
|
5133
5775
|
[mode]="mode"
|
|
5776
|
+
[config]="formConfig"
|
|
5134
5777
|
[schemaUrl]="schemaUrl"
|
|
5135
5778
|
[submitUrl]="submitUrl"
|
|
5136
5779
|
[submitMethod]="submitMethod"
|
|
5137
5780
|
[apiEndpointKey]="apiEndpointKey"
|
|
5138
5781
|
[apiUrlEntry]="apiUrlEntry"
|
|
5782
|
+
[configPersistenceStrategy]="formConfigPersistenceStrategy"
|
|
5783
|
+
[layoutPolicy]="layoutPolicy"
|
|
5139
5784
|
[presentationModeGlobal]="mode === 'view' ? true : null"
|
|
5140
5785
|
[backConfig]="backConfig"
|
|
5141
5786
|
[actions]="formActions"
|
|
@@ -5143,7 +5788,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
5143
5788
|
(formCancel)="onCancel()"
|
|
5144
5789
|
></praxis-dynamic-form>
|
|
5145
5790
|
</mat-dialog-content>
|
|
5146
|
-
`, styles: ["
|
|
5791
|
+
`, 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.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.mat-icon-button{color:var(--md-sys-color-on-surface-variant)}praxis-dynamic-form-dialog-host .dialog-header button.mat-icon-button:hover{color:var(--md-sys-color-primary);background:var(--md-sys-color-primary-container)}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
5792
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
5148
5793
|
type: Inject,
|
|
5149
5794
|
args: [MatDialogRef]
|
|
@@ -5170,6 +5815,17 @@ function stringOrUndefined(value) {
|
|
|
5170
5815
|
const text = String(value ?? '').trim();
|
|
5171
5816
|
return text || undefined;
|
|
5172
5817
|
}
|
|
5818
|
+
function sameTitle(left, right) {
|
|
5819
|
+
return normalizeTitle(left) === normalizeTitle(right);
|
|
5820
|
+
}
|
|
5821
|
+
function normalizeTitle(value) {
|
|
5822
|
+
return value
|
|
5823
|
+
.normalize('NFD')
|
|
5824
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
5825
|
+
.replace(/\s+/g, ' ')
|
|
5826
|
+
.trim()
|
|
5827
|
+
.toLocaleLowerCase('pt-BR');
|
|
5828
|
+
}
|
|
5173
5829
|
function deriveCreateSubmitLabel(actionLabel) {
|
|
5174
5830
|
if (!actionLabel) {
|
|
5175
5831
|
return 'Criar';
|
|
@@ -5180,6 +5836,61 @@ function deriveCreateSubmitLabel(actionLabel) {
|
|
|
5180
5836
|
}
|
|
5181
5837
|
return actionLabel;
|
|
5182
5838
|
}
|
|
5839
|
+
function deriveModeTitle(mode, resourceTitle) {
|
|
5840
|
+
if (!resourceTitle) {
|
|
5841
|
+
return undefined;
|
|
5842
|
+
}
|
|
5843
|
+
const singular = lowerFirstLetter(singularizePtBrResourceTitle(resourceTitle));
|
|
5844
|
+
if (mode === 'create') {
|
|
5845
|
+
return `Adicionar ${singular}`;
|
|
5846
|
+
}
|
|
5847
|
+
if (mode === 'edit') {
|
|
5848
|
+
return `Editar ${singular}`;
|
|
5849
|
+
}
|
|
5850
|
+
return `Visualizar ${singular}`;
|
|
5851
|
+
}
|
|
5852
|
+
function titleFromResourcePath(resourcePath) {
|
|
5853
|
+
const lastSegment = resourcePath?.split('/').filter(Boolean).pop();
|
|
5854
|
+
if (!lastSegment) {
|
|
5855
|
+
return undefined;
|
|
5856
|
+
}
|
|
5857
|
+
return lastSegment
|
|
5858
|
+
.replace(/[-_]+/g, ' ')
|
|
5859
|
+
.replace(/\s+/g, ' ')
|
|
5860
|
+
.trim()
|
|
5861
|
+
.toLocaleLowerCase('pt-BR');
|
|
5862
|
+
}
|
|
5863
|
+
function singularizePtBrResourceTitle(title) {
|
|
5864
|
+
const normalized = title.trim();
|
|
5865
|
+
const lower = normalized.toLocaleLowerCase('pt-BR');
|
|
5866
|
+
if (lower === 'códigos de frequência' || lower === 'codigos de frequencia') {
|
|
5867
|
+
return 'código de frequência';
|
|
5868
|
+
}
|
|
5869
|
+
const [first, ...rest] = normalized.split(/\s+/);
|
|
5870
|
+
const singularFirst = singularizePtBrWord(first);
|
|
5871
|
+
return [singularFirst, ...rest].join(' ');
|
|
5872
|
+
}
|
|
5873
|
+
function singularizePtBrWord(word) {
|
|
5874
|
+
if (/ões$/i.test(word)) {
|
|
5875
|
+
return word.replace(/ões$/i, 'ão');
|
|
5876
|
+
}
|
|
5877
|
+
if (/ais$/i.test(word)) {
|
|
5878
|
+
return word.replace(/ais$/i, 'al');
|
|
5879
|
+
}
|
|
5880
|
+
if (/eis$/i.test(word)) {
|
|
5881
|
+
return word.replace(/eis$/i, 'el');
|
|
5882
|
+
}
|
|
5883
|
+
if (/res$/i.test(word)) {
|
|
5884
|
+
return word.replace(/es$/i, '');
|
|
5885
|
+
}
|
|
5886
|
+
if (/s$/i.test(word) && !/ss$/i.test(word)) {
|
|
5887
|
+
return word.replace(/s$/i, '');
|
|
5888
|
+
}
|
|
5889
|
+
return word;
|
|
5890
|
+
}
|
|
5891
|
+
function lowerFirstLetter(value) {
|
|
5892
|
+
return value.replace(/\p{L}/u, (letter) => letter.toLocaleLowerCase('pt-BR'));
|
|
5893
|
+
}
|
|
5183
5894
|
|
|
5184
5895
|
var dynamicFormDialogHost_component = /*#__PURE__*/Object.freeze({
|
|
5185
5896
|
__proto__: null,
|
|
@@ -5229,6 +5940,7 @@ class PraxisCrudWidgetConfigEditor {
|
|
|
5229
5940
|
componentInstanceId: this.inputs?.componentInstanceId ?? this.widgetKey,
|
|
5230
5941
|
context: this.inputs?.context,
|
|
5231
5942
|
enableCustomization: this.inputs?.enableCustomization ?? false,
|
|
5943
|
+
authoringCapability: this.inputs?.authoringCapability ?? null,
|
|
5232
5944
|
};
|
|
5233
5945
|
return {
|
|
5234
5946
|
inputs,
|
|
@@ -5325,11 +6037,31 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5325
6037
|
type: 'PraxisDataQueryContext | null',
|
|
5326
6038
|
description: 'Contexto semântico de consulta encaminhado para a tabela interna do CRUD. Preferir este contrato para novo authoring remoto.',
|
|
5327
6039
|
},
|
|
6040
|
+
{
|
|
6041
|
+
name: 'metadata.resource.schemaPath',
|
|
6042
|
+
type: 'string | null',
|
|
6043
|
+
description: 'Template canonico opcional usado apenas para resolver /schemas/filtered quando resource.path precisa ser um alvo operacional concreto.',
|
|
6044
|
+
},
|
|
6045
|
+
{
|
|
6046
|
+
name: 'metadata.resource.title',
|
|
6047
|
+
type: 'string | null',
|
|
6048
|
+
description: 'Titulo documental do recurso usado pelo shell CRUD para derivar titulos contextuais de criacao, edicao e visualizacao quando a action nao informa label.',
|
|
6049
|
+
},
|
|
6050
|
+
{
|
|
6051
|
+
name: 'metadata.resource.formTitle',
|
|
6052
|
+
type: 'string | null',
|
|
6053
|
+
description: 'Titulo documental especifico para formularios do recurso, usado antes de metadata.resource.title no shell modal/drawer.',
|
|
6054
|
+
},
|
|
5328
6055
|
{
|
|
5329
6056
|
name: 'metadata.filterCriteria',
|
|
5330
6057
|
type: 'Record<string, unknown> | null',
|
|
5331
6058
|
description: 'Bridge declarativa de filtros encaminhada para a tabela interna. Para novo authoring, prefira metadata.queryContext.',
|
|
5332
6059
|
},
|
|
6060
|
+
{
|
|
6061
|
+
name: 'metadata.form',
|
|
6062
|
+
type: 'FormConfig',
|
|
6063
|
+
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.',
|
|
6064
|
+
},
|
|
5333
6065
|
{
|
|
5334
6066
|
name: 'metadata.actions[].form',
|
|
5335
6067
|
type: 'CrudActionFormContract',
|
|
@@ -5341,6 +6073,12 @@ const PRAXIS_CRUD_COMPONENT_METADATA = {
|
|
|
5341
6073
|
default: false,
|
|
5342
6074
|
description: 'Habilita modo de customização do layout.',
|
|
5343
6075
|
},
|
|
6076
|
+
{
|
|
6077
|
+
name: 'authoringCapability',
|
|
6078
|
+
type: 'string | null',
|
|
6079
|
+
default: null,
|
|
6080
|
+
description: 'Capability publica do contexto corporativo exigida para disponibilizar authoring governado no CRUD e na tabela interna.',
|
|
6081
|
+
},
|
|
5344
6082
|
],
|
|
5345
6083
|
outputs: [
|
|
5346
6084
|
{
|
|
@@ -5699,6 +6437,7 @@ const CRUD_AI_CAPABILITIES = {
|
|
|
5699
6437
|
{ path: 'actions[].params[].to', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.paramTarget, description: 'Destino do parametro.' },
|
|
5700
6438
|
{ path: 'actions[].params[].name', category: 'actions', valueKind: 'string', description: 'Nome do parametro no destino.' },
|
|
5701
6439
|
{ path: 'actions[].form.initialValue', category: 'actions', valueKind: 'object', description: 'Seed fixo do formulario injetado em inputs.initialValue antes da abertura.' },
|
|
6440
|
+
{ 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
6441
|
{ path: 'actions[].back', category: 'navigation', valueKind: 'object', description: 'BackConfig por acao.' },
|
|
5703
6442
|
{ path: 'actions[].back.strategy', category: 'navigation', valueKind: 'enum', allowedValues: ENUMS.backStrategy, description: 'Estrategia de retorno da acao (auto, close, navigate).' },
|
|
5704
6443
|
{ path: 'actions[].back.returnTo', category: 'navigation', valueKind: 'string', description: 'Rota ou destino usado quando a estrategia de retorno for navigate.' },
|
|
@@ -5736,6 +6475,7 @@ const surfaceConfigureSchema = {
|
|
|
5736
6475
|
submitMethod: { enum: ['post', 'put', 'patch', 'delete'] },
|
|
5737
6476
|
apiEndpointKey: { type: 'string' },
|
|
5738
6477
|
initialValue: { type: 'object' },
|
|
6478
|
+
layoutPolicy: { type: 'object' },
|
|
5739
6479
|
},
|
|
5740
6480
|
},
|
|
5741
6481
|
params: {
|
|
@@ -5849,19 +6589,21 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5849
6589
|
{ name: 'crudId', type: 'string', description: 'Stable CRUD instance id used for table/form identity and persistence.' },
|
|
5850
6590
|
{ name: 'componentInstanceId', type: 'string', description: 'Optional stable host instance id for multiple CRUD widgets on the same route.' },
|
|
5851
6591
|
{ name: 'context', type: 'Record<string, unknown>', description: 'Opaque host context used for authoring seeds and launcher inputs.' },
|
|
6592
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Explicit host opt-in for CRUD authoring surfaces.' },
|
|
6593
|
+
{ name: 'authoringCapability', type: 'string | null', description: 'Public runtime capability required before CRUD and delegated table authoring can open.' },
|
|
5852
6594
|
{ name: 'afterOpen', type: '{ mode: FormOpenMode; action: string }', description: 'Emitted after a CRUD action opens.' },
|
|
5853
6595
|
{ name: 'afterSave', type: '{ id: string | number; data: unknown }', description: 'Emitted after save; CRUD refetches the list.' },
|
|
5854
6596
|
{ name: 'afterDelete', type: '{ id: string | number }', description: 'Emitted after delete; CRUD refetches the list.' },
|
|
5855
6597
|
],
|
|
5856
6598
|
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.' },
|
|
6599
|
+
{ 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
6600
|
{ kind: 'listSurface', resolver: 'crud-list-surface', description: 'CRUD-hosted list surface and table delegation boundary.' },
|
|
5859
6601
|
{ kind: 'createSurface', resolver: 'crud-action-by-id:create', description: 'Create action open mode, route/form binding and launcher inputs.' },
|
|
5860
6602
|
{ kind: 'editSurface', resolver: 'crud-action-by-id:edit', description: 'Edit action open mode, route/form binding and launcher inputs.' },
|
|
5861
6603
|
{ kind: 'viewSurface', resolver: 'crud-action-by-id:view', description: 'View action open mode, route/form binding and launcher inputs.' },
|
|
5862
6604
|
{ 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.' },
|
|
6605
|
+
{ 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.' },
|
|
6606
|
+
{ 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
6607
|
{ kind: 'permissions', resolver: 'crud-resource-capabilities', description: 'CRUD action availability derived from resource capabilities and action permissions.' },
|
|
5866
6608
|
{ 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
6609
|
{ kind: 'childOperation', resolver: 'child-authoring-manifest-operation', description: 'Delegated form/table/dialog/settings-panel operation owned by the child component manifest.' },
|
|
@@ -5877,13 +6619,13 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
5877
6619
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-resource-bind', handlerContract: {
|
|
5878
6620
|
reads: ['CrudMetadata.resource', 'api_metadata', 'ResourceDiscoveryService', 'GET /{resource}/capabilities'],
|
|
5879
6621
|
writes: ['CrudMetadata.resource', 'CrudMetadata.queryContext', 'PraxisCrudComponent.tableCrudContext'],
|
|
5880
|
-
identityKeys: ['resourcePath', 'resourceKey'],
|
|
6622
|
+
identityKeys: ['resourcePath', 'resourceKey', 'schemaPath'],
|
|
5881
6623
|
inputSchema: resourceBindSchema,
|
|
5882
6624
|
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.',
|
|
6625
|
+
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
6626
|
} }],
|
|
5885
6627
|
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'],
|
|
6628
|
+
affectedPaths: ['resource.path', 'resource.schemaPath', 'resource.idField', 'resource.endpointKey', 'resource.apiUrlEntry', 'queryContext', 'filterCriteria'],
|
|
5887
6629
|
submissionImpact: 'affects-remote-binding',
|
|
5888
6630
|
preconditions: ['crud-metadata-loaded', 'api-metadata-available'],
|
|
5889
6631
|
},
|
|
@@ -6017,14 +6759,14 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6017
6759
|
target: { kind: 'dialogHost', resolver: 'crud-dialog-host-defaults', ambiguityPolicy: 'fail', required: true },
|
|
6018
6760
|
inputSchema: dialogHostSchema,
|
|
6019
6761
|
effects: [{ kind: 'compile-domain-patch', handler: 'crud-dialog-host-set', handlerContract: {
|
|
6020
|
-
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'CRUD_DRAWER_ADAPTER'],
|
|
6762
|
+
reads: ['CrudMetadata.defaults', 'CrudLauncherService.resolveOpenMode', 'DialogService', 'DynamicFormDialogHostComponent', 'CRUD_DRAWER_ADAPTER'],
|
|
6021
6763
|
writes: ['CrudMetadata.defaults.openMode', 'CrudMetadata.defaults.modal', 'CrudMetadata.defaults.back'],
|
|
6022
6764
|
identityKeys: ['crudId'],
|
|
6023
6765
|
inputSchema: dialogHostSchema,
|
|
6024
|
-
failureModes: ['open-mode-unsupported', 'drawer-
|
|
6025
|
-
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher
|
|
6766
|
+
failureModes: ['open-mode-unsupported', 'drawer-runtime-unavailable', 'modal-size-invalid', 'back-policy-invalid'],
|
|
6767
|
+
description: 'Configures CRUD-owned route/modal/drawer defaults consumed by the launcher, default dialog-backed drawer runtime and optional host drawer adapter.',
|
|
6026
6768
|
} }],
|
|
6027
|
-
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-
|
|
6769
|
+
validators: ['open-mode-supported', 'modal-size-valid', 'drawer-runtime-available', 'back-policy-valid', 'settings-panel-shell-compatible'],
|
|
6028
6770
|
affectedPaths: ['defaults.openMode', 'defaults.modal', 'defaults.back'],
|
|
6029
6771
|
submissionImpact: 'config-only',
|
|
6030
6772
|
preconditions: ['crud-metadata-loaded'],
|
|
@@ -6100,7 +6842,7 @@ const PRAXIS_CRUD_AUTHORING_MANIFEST = {
|
|
|
6100
6842
|
{ validatorId: 'permissions-delete-valid', level: 'error', code: 'CRUD_DELETE_PERMISSION_VALID', description: 'Delete permission cannot bypass resource capabilities or confirmation policy.' },
|
|
6101
6843
|
{ validatorId: 'open-mode-supported', level: 'error', code: 'CRUD_OPEN_MODE_SUPPORTED', description: 'Open mode must be route, modal or drawer.' },
|
|
6102
6844
|
{ validatorId: 'modal-size-valid', level: 'error', code: 'CRUD_MODAL_SIZE_VALID', description: 'Modal sizing defaults must be valid DialogConfig values.' },
|
|
6103
|
-
{ validatorId: 'drawer-
|
|
6845
|
+
{ 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.' },
|
|
6104
6846
|
{ validatorId: 'back-policy-valid', level: 'error', code: 'CRUD_BACK_POLICY_VALID', description: 'Back policy must be valid for route/modal/drawer behavior.' },
|
|
6105
6847
|
{ validatorId: 'settings-panel-shell-compatible', level: 'warning', code: 'CRUD_SETTINGS_PANEL_COMPATIBLE', description: 'Authoring shell must preserve apply/save/reset semantics.' },
|
|
6106
6848
|
{ validatorId: 'action-permission-supported', level: 'error', code: 'CRUD_ACTION_PERMISSION_SUPPORTED', description: 'Action permissions must map to supported resource capabilities.' },
|