@masterteam/forms 0.0.34 → 0.0.36

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.
@@ -0,0 +1,598 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, Injectable, signal, computed, input, output, effect, untracked, Component } from '@angular/core';
3
+ import * as i1 from '@angular/forms';
4
+ import { FormControl, ReactiveFormsModule } from '@angular/forms';
5
+ import * as i2 from '@angular/common';
6
+ import { CommonModule } from '@angular/common';
7
+ import { DynamicForm } from '@masterteam/forms/dynamic-form';
8
+ import { HttpClient } from '@angular/common/http';
9
+
10
+ /**
11
+ * Stateless HTTP service for process-forms runtime APIs.
12
+ * Root-provided — safe to share across multiple ClientForm instances.
13
+ */
14
+ class ClientFormApiService {
15
+ http = inject(HttpClient);
16
+ baseUrl = 'process-forms';
17
+ /**
18
+ * Load form configuration and values for a given operation context.
19
+ * Backend determines mode (Approval vs Direct) based on published schema.
20
+ */
21
+ load(request) {
22
+ return this.http.post(`${this.baseUrl}/load`, request);
23
+ }
24
+ /**
25
+ * Submit form values. Result depends on mode:
26
+ * - Approval → status: 'PendingApproval'
27
+ * - Direct → status: 'Executed'
28
+ */
29
+ submit(request) {
30
+ return this.http.post(`${this.baseUrl}/submit`, request);
31
+ }
32
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
33
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormApiService, providedIn: 'root' });
34
+ }
35
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormApiService, decorators: [{
36
+ type: Injectable,
37
+ args: [{ providedIn: 'root' }]
38
+ }] });
39
+
40
+ /**
41
+ * Per-instance signal-based state for ClientForm.
42
+ *
43
+ * NOT providedIn root — each ClientForm component provides its own instance
44
+ * via `providers: [ClientFormStateService]`, enabling multiple independent
45
+ * forms on the same page.
46
+ */
47
+ class ClientFormStateService {
48
+ // ============================================================================
49
+ // Core State Signals
50
+ // ============================================================================
51
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
52
+ submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
53
+ error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
54
+ submitError = signal(null, ...(ngDevMode ? [{ debugName: "submitError" }] : []));
55
+ loadResponse = signal(null, ...(ngDevMode ? [{ debugName: "loadResponse" }] : []));
56
+ submitResponse = signal(null, ...(ngDevMode ? [{ debugName: "submitResponse" }] : []));
57
+ // ============================================================================
58
+ // Derived Computeds — Load Response
59
+ // ============================================================================
60
+ isLoaded = computed(() => !!this.loadResponse(), ...(ngDevMode ? [{ debugName: "isLoaded" }] : []));
61
+ mode = computed(() => this.loadResponse()?.mode ?? null, ...(ngDevMode ? [{ debugName: "mode" }] : []));
62
+ isApproval = computed(() => this.mode() === 'Approval', ...(ngDevMode ? [{ debugName: "isApproval" }] : []));
63
+ isDirect = computed(() => this.mode() === 'Direct', ...(ngDevMode ? [{ debugName: "isDirect" }] : []));
64
+ formSource = computed(() => this.loadResponse()?.formSource ?? null, ...(ngDevMode ? [{ debugName: "formSource" }] : []));
65
+ isFallbackForm = computed(() => {
66
+ const source = this.formSource();
67
+ return source === 'ModuleFallback' || source === 'LevelFallback';
68
+ }, ...(ngDevMode ? [{ debugName: "isFallbackForm" }] : []));
69
+ requiresForm = computed(() => this.loadResponse()?.requiresForm ?? false, ...(ngDevMode ? [{ debugName: "requiresForm" }] : []));
70
+ formConfiguration = computed(() => this.loadResponse()?.formConfiguration ?? null, ...(ngDevMode ? [{ debugName: "formConfiguration" }] : []));
71
+ values = computed(() => this.loadResponse()?.values ?? [], ...(ngDevMode ? [{ debugName: "values" }] : []));
72
+ context = computed(() => this.loadResponse()?.context ?? null, ...(ngDevMode ? [{ debugName: "context" }] : []));
73
+ stepName = computed(() => this.loadResponse()?.stepName ?? null, ...(ngDevMode ? [{ debugName: "stepName" }] : []));
74
+ requestSchemaId = computed(() => this.loadResponse()?.requestSchemaId ?? null, ...(ngDevMode ? [{ debugName: "requestSchemaId" }] : []));
75
+ requestId = computed(() => this.loadResponse()?.requestId ?? null, ...(ngDevMode ? [{ debugName: "requestId" }] : []));
76
+ stepId = computed(() => this.loadResponse()?.stepId ?? null, ...(ngDevMode ? [{ debugName: "stepId" }] : []));
77
+ stepSchemaId = computed(() => this.loadResponse()?.stepSchemaId ?? null, ...(ngDevMode ? [{ debugName: "stepSchemaId" }] : []));
78
+ // ============================================================================
79
+ // Derived Computeds — Value Categories
80
+ // ============================================================================
81
+ /** Process virtual fields (Request_Date, Step_Name, etc.) — read-only display */
82
+ virtualFields = computed(() => this.values().filter((v) => v.metadata?.source === 'ProcessVirtual'), ...(ngDevMode ? [{ debugName: "virtualFields" }] : []));
83
+ /** Editable form values (non-virtual) */
84
+ formValues = computed(() => this.values().filter((v) => v.metadata?.source !== 'ProcessVirtual'), ...(ngDevMode ? [{ debugName: "formValues" }] : []));
85
+ // ============================================================================
86
+ // Derived Computeds — Submit Response
87
+ // ============================================================================
88
+ isSubmitted = computed(() => !!this.submitResponse(), ...(ngDevMode ? [{ debugName: "isSubmitted" }] : []));
89
+ submitStatus = computed(() => this.submitResponse()?.status ?? null, ...(ngDevMode ? [{ debugName: "submitStatus" }] : []));
90
+ isPendingApproval = computed(() => this.submitStatus() === 'PendingApproval', ...(ngDevMode ? [{ debugName: "isPendingApproval" }] : []));
91
+ isExecuted = computed(() => this.submitStatus() === 'Executed', ...(ngDevMode ? [{ debugName: "isExecuted" }] : []));
92
+ createdEntityId = computed(() => this.submitResponse()?.createdEntityId ?? null, ...(ngDevMode ? [{ debugName: "createdEntityId" }] : []));
93
+ // ============================================================================
94
+ // State Mutations
95
+ // ============================================================================
96
+ setLoadResponse(response) {
97
+ this.loadResponse.set(response);
98
+ this.error.set(null);
99
+ }
100
+ setSubmitResponse(response) {
101
+ this.submitResponse.set(response);
102
+ this.submitError.set(null);
103
+ }
104
+ setError(message) {
105
+ this.error.set(message);
106
+ this.loading.set(false);
107
+ }
108
+ setSubmitError(message) {
109
+ this.submitError.set(message);
110
+ this.submitting.set(false);
111
+ }
112
+ reset() {
113
+ this.loading.set(false);
114
+ this.submitting.set(false);
115
+ this.error.set(null);
116
+ this.submitError.set(null);
117
+ this.loadResponse.set(null);
118
+ this.submitResponse.set(null);
119
+ }
120
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
121
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormStateService });
122
+ }
123
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormStateService, decorators: [{
124
+ type: Injectable
125
+ }] });
126
+
127
+ // ============================================================================
128
+ // viewType → DynamicField type mapping
129
+ // ============================================================================
130
+ const VIEW_TYPE_MAP = {
131
+ User: 'select',
132
+ Text: 'text',
133
+ LongText: 'editor-field',
134
+ Percentage: 'slider',
135
+ Date: 'date',
136
+ Currency: 'text',
137
+ Number: 'number',
138
+ Lookup: 'select',
139
+ LookupMultiSelect: 'multi-select',
140
+ Checkbox: 'toggle',
141
+ InternalModule: 'select',
142
+ DynamicList: 'select',
143
+ API: 'select',
144
+ Time: 'date',
145
+ Status: 'select',
146
+ Attachment: 'upload-file',
147
+ EditableListView: 'text',
148
+ LookupLog: 'text',
149
+ LookupMatrix: 'select',
150
+ Location: 'select',
151
+ };
152
+ const WIDTH_TO_COLSPAN = {
153
+ '25': 3,
154
+ '50': 6,
155
+ '100': 12,
156
+ };
157
+ // ============================================================================
158
+ // Public Mapper Functions
159
+ // ============================================================================
160
+ /**
161
+ * Convert a runtime FormConfiguration into a DynamicFormConfig
162
+ * that can be passed directly to `<mt-dynamic-form>`.
163
+ *
164
+ * @param config The form configuration from the load API
165
+ * @param lang Current UI language ('en' | 'ar')
166
+ * @param mode 'create' or 'edit' — filters hidden fields accordingly
167
+ */
168
+ function mapToDynamicFormConfig(config, lang = 'en', mode = 'create') {
169
+ return {
170
+ sections: config.sections
171
+ .slice()
172
+ .sort((a, b) => a.order - b.order)
173
+ .map((section) => {
174
+ const sectionName = section.name[lang] ?? section.name['en'] ?? '';
175
+ const visibleFields = section.fields
176
+ .filter((field) => {
177
+ // isRead=false → completely hidden
178
+ if (field.isRead === false)
179
+ return false;
180
+ if (mode === 'create')
181
+ return !field.hiddenInCreation;
182
+ return !field.hiddenInEditForm;
183
+ })
184
+ .sort((a, b) => a.order - b.order);
185
+ return {
186
+ key: section.id,
187
+ label: sectionName,
188
+ type: 'header',
189
+ columns: 12,
190
+ order: section.order,
191
+ fields: visibleFields.map((field) => mapFieldToConfig(field, lang)),
192
+ };
193
+ })
194
+ .filter((section) => section.fields.length > 0),
195
+ };
196
+ }
197
+ /**
198
+ * Convert API property values into a flat key-value object
199
+ * suitable for `formControl.patchValue()`.
200
+ *
201
+ * Only includes non-virtual (editable) values.
202
+ */
203
+ function mapValuesToFormValue(values) {
204
+ const result = {};
205
+ for (const v of values) {
206
+ if (v.metadata?.source === 'ProcessVirtual')
207
+ continue;
208
+ result[v.propertyKey] = v.value;
209
+ }
210
+ return result;
211
+ }
212
+ /**
213
+ * Convert the current form value back into the submit payload format.
214
+ *
215
+ * Maps `requestPropertyId` from the load response metadata where available,
216
+ * so backend can match to schema request properties.
217
+ */
218
+ function mapFormValueToSubmitValues(formValue, loadResponse) {
219
+ const metadataByKey = new Map();
220
+ for (const v of loadResponse.values) {
221
+ if (v.metadata) {
222
+ metadataByKey.set(v.propertyKey, {
223
+ propertyId: v.metadata.propertyId,
224
+ });
225
+ }
226
+ }
227
+ return Object.entries(formValue)
228
+ .filter(([, value]) => value !== undefined && value !== null)
229
+ .map(([propertyKey, value]) => {
230
+ const meta = metadataByKey.get(propertyKey);
231
+ const submitValue = { propertyKey, value };
232
+ if (meta?.propertyId) {
233
+ submitValue.requestPropertyId = meta.propertyId;
234
+ }
235
+ return submitValue;
236
+ });
237
+ }
238
+ // ============================================================================
239
+ // Internal Helpers
240
+ // ============================================================================
241
+ function mapFieldToConfig(field, lang) {
242
+ const viewType = field.property?.viewType ?? 'Text';
243
+ const fieldType = VIEW_TYPE_MAP[viewType] ?? 'text';
244
+ const label = resolvePropertyName(field.property, lang);
245
+ const colSpan = WIDTH_TO_COLSPAN[field.width] ?? 12;
246
+ const config = {
247
+ key: field.propertyKey,
248
+ label,
249
+ type: fieldType,
250
+ colSpan,
251
+ order: field.order,
252
+ placeholder: label,
253
+ required: field.isRequired ?? false,
254
+ readonly: field.isWrite === false,
255
+ validators: field.isRequired
256
+ ? [{ type: 'required', message: `${label} is required` }]
257
+ : [],
258
+ };
259
+ // Enrich select-type fields with options from property configuration
260
+ if (fieldType === 'select' || fieldType === 'multi-select') {
261
+ const options = extractOptionsFromProperty(field.property);
262
+ if (options) {
263
+ config.options = options;
264
+ config.optionLabel = 'label';
265
+ config.optionValue = 'value';
266
+ }
267
+ }
268
+ return config;
269
+ }
270
+ function resolvePropertyName(property, lang) {
271
+ if (!property?.name)
272
+ return '';
273
+ if (typeof property.name === 'string')
274
+ return property.name;
275
+ return property.name[lang] ?? property.name['en'] ?? '';
276
+ }
277
+ function extractOptionsFromProperty(property) {
278
+ if (!property?.configuration)
279
+ return null;
280
+ // Options may be in different shapes depending on property configuration
281
+ const config = property.configuration;
282
+ if (Array.isArray(config['options'])) {
283
+ return config['options'];
284
+ }
285
+ if (Array.isArray(config['items'])) {
286
+ return config['items'].map((item) => ({
287
+ label: typeof item.name === 'string'
288
+ ? item.name
289
+ : (item.name?.['en'] ?? item.label ?? String(item.value)),
290
+ value: item.id ?? item.value ?? item.key,
291
+ }));
292
+ }
293
+ return null;
294
+ }
295
+
296
+ /**
297
+ * Client Form — Runtime process form component.
298
+ *
299
+ * Self-contained, signal-based (no NGXS). Each instance manages its own state
300
+ * via a component-scoped `ClientFormStateService`.
301
+ *
302
+ * **No action buttons in template.** Parent controls all actions via `viewChild()`:
303
+ *
304
+ * ```html
305
+ * <mt-client-form #processForm [moduleKey]="'Risk'" [operationKey]="'CloseRisk'" />
306
+ * <button (click)="processForm.load()">Load</button>
307
+ * <button (click)="processForm.submit()">Submit</button>
308
+ * ```
309
+ *
310
+ * Or programmatically:
311
+ * ```typescript
312
+ * readonly processForm = viewChild.required(ClientForm);
313
+ * this.processForm().load();
314
+ * this.processForm().submit();
315
+ * ```
316
+ */
317
+ class ClientForm {
318
+ api = inject(ClientFormApiService);
319
+ state = inject(ClientFormStateService);
320
+ loadSub;
321
+ submitSub;
322
+ // ============================================================================
323
+ // Inputs — Required Context
324
+ // ============================================================================
325
+ moduleKey = input.required(...(ngDevMode ? [{ debugName: "moduleKey" }] : []));
326
+ operationKey = input.required(...(ngDevMode ? [{ debugName: "operationKey" }] : []));
327
+ // ============================================================================
328
+ // Inputs — Optional Context
329
+ // ============================================================================
330
+ moduleId = input(...(ngDevMode ? [undefined, { debugName: "moduleId" }] : []));
331
+ levelId = input(...(ngDevMode ? [undefined, { debugName: "levelId" }] : []));
332
+ levelDataId = input(...(ngDevMode ? [undefined, { debugName: "levelDataId" }] : []));
333
+ moduleDataId = input(...(ngDevMode ? [undefined, { debugName: "moduleDataId" }] : []));
334
+ requestSchemaId = input(...(ngDevMode ? [undefined, { debugName: "requestSchemaId" }] : []));
335
+ draftProcessId = input(...(ngDevMode ? [undefined, { debugName: "draftProcessId" }] : []));
336
+ preview = input(false, ...(ngDevMode ? [{ debugName: "preview" }] : []));
337
+ returnUrl = input(...(ngDevMode ? [undefined, { debugName: "returnUrl" }] : []));
338
+ // ============================================================================
339
+ // Inputs — UI Configuration
340
+ // ============================================================================
341
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
342
+ autoLoad = input(true, ...(ngDevMode ? [{ debugName: "autoLoad" }] : []));
343
+ formMode = input('create', ...(ngDevMode ? [{ debugName: "formMode" }] : []));
344
+ lang = input('en', ...(ngDevMode ? [{ debugName: "lang" }] : []));
345
+ // ============================================================================
346
+ // Outputs
347
+ // ============================================================================
348
+ loaded = output();
349
+ submitted = output();
350
+ errored = output();
351
+ modeDetected = output();
352
+ formSourceDetected = output();
353
+ // ============================================================================
354
+ // Internal Form Control
355
+ // ============================================================================
356
+ formControl = new FormControl({});
357
+ // ============================================================================
358
+ // Computed — Dynamic Form Config
359
+ // ============================================================================
360
+ formConfig = computed(() => {
361
+ const config = this.state.formConfiguration();
362
+ if (!config)
363
+ return null;
364
+ return mapToDynamicFormConfig(config, this.lang(), this.formMode());
365
+ }, ...(ngDevMode ? [{ debugName: "formConfig" }] : []));
366
+ initialValues = computed(() => {
367
+ return mapValuesToFormValue(this.state.formValues());
368
+ }, ...(ngDevMode ? [{ debugName: "initialValues" }] : []));
369
+ virtualFields = computed(() => this.state.virtualFields(), ...(ngDevMode ? [{ debugName: "virtualFields" }] : []));
370
+ hasVirtualFields = computed(() => this.virtualFields().length > 0, ...(ngDevMode ? [{ debugName: "hasVirtualFields" }] : []));
371
+ // ============================================================================
372
+ // Auto-Load Effect
373
+ // ============================================================================
374
+ autoLoadEffect = effect(() => {
375
+ const autoLoad = this.autoLoad();
376
+ const moduleKey = this.moduleKey();
377
+ const operationKey = this.operationKey();
378
+ if (autoLoad && moduleKey && operationKey) {
379
+ untracked(() => this.load());
380
+ }
381
+ }, ...(ngDevMode ? [{ debugName: "autoLoadEffect" }] : []));
382
+ // ============================================================================
383
+ // Patch Values Effect — applies initial values after load
384
+ // ============================================================================
385
+ patchValuesEffect = effect(() => {
386
+ const values = this.initialValues();
387
+ const isLoaded = this.state.isLoaded();
388
+ if (isLoaded && Object.keys(values).length > 0) {
389
+ untracked(() => {
390
+ this.formControl.patchValue(values, { emitEvent: false });
391
+ });
392
+ }
393
+ }, ...(ngDevMode ? [{ debugName: "patchValuesEffect" }] : []));
394
+ // ============================================================================
395
+ // Public API (accessed via viewChild)
396
+ // ============================================================================
397
+ /**
398
+ * Load form configuration from the API.
399
+ * Builds request from current input values.
400
+ */
401
+ load() {
402
+ if (this.state.loading())
403
+ return;
404
+ this.loadSub?.unsubscribe();
405
+ this.state.loading.set(true);
406
+ this.state.error.set(null);
407
+ this.state.submitResponse.set(null);
408
+ const request = this.buildLoadRequest();
409
+ this.loadSub = this.api.load(request).subscribe({
410
+ next: (response) => {
411
+ this.state.loading.set(false);
412
+ if (response.data) {
413
+ this.state.setLoadResponse(response.data);
414
+ this.loaded.emit(response.data);
415
+ if (response.data.mode) {
416
+ this.modeDetected.emit(response.data.mode);
417
+ }
418
+ if (response.data.formSource) {
419
+ this.formSourceDetected.emit(response.data.formSource);
420
+ }
421
+ }
422
+ else {
423
+ const msg = response.message ?? 'Failed to load form';
424
+ this.state.setError(msg);
425
+ this.errored.emit(msg);
426
+ }
427
+ },
428
+ error: (err) => {
429
+ const msg = err?.error?.message ?? err?.message ?? 'Failed to load form';
430
+ this.state.setError(msg);
431
+ this.errored.emit(msg);
432
+ },
433
+ });
434
+ }
435
+ /**
436
+ * Submit the current form values.
437
+ * Builds submit request from form value + load context.
438
+ */
439
+ submit() {
440
+ if (this.state.submitting())
441
+ return;
442
+ this.submitSub?.unsubscribe();
443
+ this.state.submitting.set(true);
444
+ this.state.submitError.set(null);
445
+ const request = this.buildSubmitRequest();
446
+ this.submitSub = this.api.submit(request).subscribe({
447
+ next: (response) => {
448
+ this.state.submitting.set(false);
449
+ if (response.data) {
450
+ this.state.setSubmitResponse(response.data);
451
+ this.submitted.emit(response.data);
452
+ }
453
+ else {
454
+ const msg = response.message ?? 'Failed to submit form';
455
+ this.state.setSubmitError(msg);
456
+ this.errored.emit(msg);
457
+ }
458
+ },
459
+ error: (err) => {
460
+ const msg = err?.error?.message ?? err?.message ?? 'Failed to submit form';
461
+ this.state.setSubmitError(msg);
462
+ this.errored.emit(msg);
463
+ },
464
+ });
465
+ }
466
+ /**
467
+ * Get the current form value as a flat key-value object.
468
+ */
469
+ getFormValue() {
470
+ return this.formControl.value ?? {};
471
+ }
472
+ /**
473
+ * Get the current form value mapped to submit payload format.
474
+ */
475
+ getSubmitValues() {
476
+ const loadResponse = this.state.loadResponse();
477
+ if (!loadResponse)
478
+ return [];
479
+ return mapFormValueToSubmitValues(this.getFormValue(), loadResponse);
480
+ }
481
+ /**
482
+ * Check whether the current form state is valid.
483
+ */
484
+ isValid() {
485
+ return this.formControl.valid;
486
+ }
487
+ /**
488
+ * Reset the component to its initial state.
489
+ */
490
+ reset() {
491
+ this.loadSub?.unsubscribe();
492
+ this.submitSub?.unsubscribe();
493
+ this.formControl.reset({});
494
+ this.state.reset();
495
+ }
496
+ // ============================================================================
497
+ // Lifecycle
498
+ // ============================================================================
499
+ ngOnDestroy() {
500
+ this.loadSub?.unsubscribe();
501
+ this.submitSub?.unsubscribe();
502
+ }
503
+ // ============================================================================
504
+ // Private Helpers
505
+ // ============================================================================
506
+ buildLoadRequest() {
507
+ const req = {
508
+ moduleKey: this.moduleKey(),
509
+ operationKey: this.operationKey(),
510
+ };
511
+ const moduleId = this.moduleId();
512
+ const levelId = this.levelId();
513
+ const levelDataId = this.levelDataId();
514
+ const moduleDataId = this.moduleDataId();
515
+ const requestSchemaId = this.requestSchemaId();
516
+ const draftProcessId = this.draftProcessId();
517
+ const preview = this.preview();
518
+ if (moduleId != null)
519
+ req.moduleId = moduleId;
520
+ if (levelId != null)
521
+ req.levelId = levelId;
522
+ if (levelDataId != null)
523
+ req.levelDataId = levelDataId;
524
+ if (moduleDataId != null)
525
+ req.moduleDataId = moduleDataId;
526
+ if (requestSchemaId != null)
527
+ req.requestSchemaId = requestSchemaId;
528
+ if (draftProcessId != null)
529
+ req.draftProcessId = draftProcessId;
530
+ if (preview)
531
+ req.preview = preview;
532
+ return req;
533
+ }
534
+ buildSubmitRequest() {
535
+ const loadResponse = this.state.loadResponse();
536
+ const context = this.state.context();
537
+ const formValue = this.getFormValue();
538
+ const values = loadResponse
539
+ ? mapFormValueToSubmitValues(formValue, loadResponse)
540
+ : Object.entries(formValue)
541
+ .filter(([, v]) => v !== undefined && v !== null)
542
+ .map(([propertyKey, value]) => ({ propertyKey, value }));
543
+ const req = {
544
+ moduleKey: context?.moduleKey ?? this.moduleKey(),
545
+ operationKey: context?.operationKey ?? this.operationKey(),
546
+ values,
547
+ };
548
+ const moduleId = context?.moduleId ?? this.moduleId();
549
+ const levelId = context?.levelId ?? this.levelId();
550
+ const levelDataId = context?.levelDataId ?? this.levelDataId();
551
+ const moduleDataId = context?.moduleDataId ?? this.moduleDataId();
552
+ const requestSchemaId = context?.requestSchemaId ?? this.requestSchemaId();
553
+ const draftProcessId = this.draftProcessId();
554
+ const returnUrl = this.returnUrl();
555
+ if (moduleId != null)
556
+ req.moduleId = moduleId;
557
+ if (levelId != null)
558
+ req.levelId = levelId;
559
+ if (levelDataId != null)
560
+ req.levelDataId = levelDataId;
561
+ if (moduleDataId != null)
562
+ req.moduleDataId = moduleDataId;
563
+ if (requestSchemaId != null)
564
+ req.requestSchemaId = requestSchemaId;
565
+ if (draftProcessId != null)
566
+ req.draftProcessId = draftProcessId;
567
+ if (returnUrl)
568
+ req.returnUrl = returnUrl;
569
+ return req;
570
+ }
571
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
572
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: ClientForm, isStandalone: true, selector: "mt-client-form", inputs: { moduleKey: { classPropertyName: "moduleKey", publicName: "moduleKey", isSignal: true, isRequired: true, transformFunction: null }, operationKey: { classPropertyName: "operationKey", publicName: "operationKey", isSignal: true, isRequired: true, transformFunction: null }, moduleId: { classPropertyName: "moduleId", publicName: "moduleId", isSignal: true, isRequired: false, transformFunction: null }, levelId: { classPropertyName: "levelId", publicName: "levelId", isSignal: true, isRequired: false, transformFunction: null }, levelDataId: { classPropertyName: "levelDataId", publicName: "levelDataId", isSignal: true, isRequired: false, transformFunction: null }, moduleDataId: { classPropertyName: "moduleDataId", publicName: "moduleDataId", isSignal: true, isRequired: false, transformFunction: null }, requestSchemaId: { classPropertyName: "requestSchemaId", publicName: "requestSchemaId", isSignal: true, isRequired: false, transformFunction: null }, draftProcessId: { classPropertyName: "draftProcessId", publicName: "draftProcessId", isSignal: true, isRequired: false, transformFunction: null }, preview: { classPropertyName: "preview", publicName: "preview", isSignal: true, isRequired: false, transformFunction: null }, returnUrl: { classPropertyName: "returnUrl", publicName: "returnUrl", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, autoLoad: { classPropertyName: "autoLoad", publicName: "autoLoad", isSignal: true, isRequired: false, transformFunction: null }, formMode: { classPropertyName: "formMode", publicName: "formMode", isSignal: true, isRequired: false, transformFunction: null }, lang: { classPropertyName: "lang", publicName: "lang", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", submitted: "submitted", errored: "errored", modeDetected: "modeDetected", formSourceDetected: "formSourceDetected" }, providers: [ClientFormStateService], ngImport: i0, template: "<!-- Client Form Template \u2014 Render only, NO action buttons -->\r\n\r\n<!-- Loading State -->\r\n@if (state.loading()) {\r\n <div class=\"flex flex-col gap-4 animate-pulse\">\r\n <div class=\"h-6 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"grid grid-cols-12 gap-4\">\r\n <div class=\"col-span-6 h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"col-span-6 h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"col-span-12 h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"col-span-6 h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"col-span-6 h-10 bg-surface-200 rounded\"></div>\r\n </div>\r\n </div>\r\n}\r\n\r\n<!-- Error State -->\r\n@if (state.error(); as error) {\r\n <div\r\n class=\"flex items-center gap-2 p-3 rounded-lg bg-red-50 text-red-700 border border-red-200\"\r\n role=\"alert\"\r\n >\r\n <svg class=\"w-5 h-5 shrink-0\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n <span class=\"text-sm font-medium\">{{ error }}</span>\r\n </div>\r\n}\r\n\r\n<!-- Loaded State -->\r\n@if (state.isLoaded() && !state.loading()) {\r\n <!-- Step Info Bar -->\r\n @if (state.stepName() || state.mode()) {\r\n <div\r\n class=\"flex items-center gap-3 mb-4 p-3 rounded-lg bg-surface-50 border border-surface-200\"\r\n >\r\n @if (state.stepName()) {\r\n <span class=\"text-sm font-semibold text-surface-700\">\r\n {{ state.stepName() }}\r\n </span>\r\n }\r\n\r\n @if (state.mode()) {\r\n <span\r\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\r\n [class]=\"\r\n state.isApproval()\r\n ? 'bg-amber-100 text-amber-800'\r\n : 'bg-emerald-100 text-emerald-800'\r\n \"\r\n >\r\n {{ state.mode() }}\r\n </span>\r\n }\r\n\r\n @if (state.isFallbackForm()) {\r\n <span\r\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-sky-100 text-sky-800\"\r\n >\r\n {{ state.formSource() }}\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Virtual Fields (read-only process context) -->\r\n @if (hasVirtualFields()) {\r\n <div class=\"mb-4 p-3 rounded-lg bg-surface-50 border border-surface-200\">\r\n <div\r\n class=\"grid grid-cols-2 gap-x-6 gap-y-2 sm:grid-cols-3 lg:grid-cols-4\"\r\n >\r\n @for (field of virtualFields(); track field.propertyKey) {\r\n <div class=\"flex flex-col gap-0.5\">\r\n <span class=\"text-xs text-muted-color font-medium\">\r\n {{ field.propertyKey | titlecase }}\r\n </span>\r\n <span class=\"text-sm text-surface-800 font-medium\">\r\n {{ field.value ?? \"\u2014\" }}\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Dynamic Form -->\r\n @if (state.requiresForm() && formConfig(); as config) {\r\n <mt-dynamic-form [formConfig]=\"config\" [formControl]=\"formControl\" />\r\n } @else if (!state.requiresForm()) {\r\n <div\r\n class=\"flex items-center justify-center p-6 rounded-lg bg-surface-50 border border-surface-200 border-dashed\"\r\n >\r\n <p class=\"text-sm text-muted-color\">\r\n No form required for this operation.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!-- Submit Error -->\r\n @if (state.submitError(); as submitError) {\r\n <div\r\n class=\"mt-4 flex items-center gap-2 p-3 rounded-lg bg-red-50 text-red-700 border border-red-200\"\r\n role=\"alert\"\r\n >\r\n <svg class=\"w-5 h-5 shrink-0\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n <span class=\"text-sm font-medium\">{{ submitError }}</span>\r\n </div>\r\n }\r\n\r\n <!-- Submit Success -->\r\n @if (state.isSubmitted()) {\r\n <div\r\n class=\"mt-4 flex items-center gap-2 p-3 rounded-lg border\"\r\n [class]=\"\r\n state.isPendingApproval()\r\n ? 'bg-amber-50 text-amber-700 border-amber-200'\r\n : 'bg-emerald-50 text-emerald-700 border-emerald-200'\r\n \"\r\n >\r\n <svg class=\"w-5 h-5 shrink-0\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n <span class=\"text-sm font-medium\">\r\n @if (state.isPendingApproval()) {\r\n Request submitted for approval.\r\n } @else {\r\n Operation executed successfully.\r\n }\r\n </span>\r\n </div>\r\n }\r\n\r\n <!-- Submitting Overlay -->\r\n @if (state.submitting()) {\r\n <div class=\"mt-4 flex items-center gap-2 text-sm text-muted-color\">\r\n <div\r\n class=\"w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin\"\r\n ></div>\r\n <span>Submitting...</span>\r\n </div>\r\n }\r\n}\r\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }] });
573
+ }
574
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientForm, decorators: [{
575
+ type: Component,
576
+ args: [{ selector: 'mt-client-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, DynamicForm], providers: [ClientFormStateService], template: "<!-- Client Form Template \u2014 Render only, NO action buttons -->\r\n\r\n<!-- Loading State -->\r\n@if (state.loading()) {\r\n <div class=\"flex flex-col gap-4 animate-pulse\">\r\n <div class=\"h-6 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"grid grid-cols-12 gap-4\">\r\n <div class=\"col-span-6 h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"col-span-6 h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"col-span-12 h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"col-span-6 h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"col-span-6 h-10 bg-surface-200 rounded\"></div>\r\n </div>\r\n </div>\r\n}\r\n\r\n<!-- Error State -->\r\n@if (state.error(); as error) {\r\n <div\r\n class=\"flex items-center gap-2 p-3 rounded-lg bg-red-50 text-red-700 border border-red-200\"\r\n role=\"alert\"\r\n >\r\n <svg class=\"w-5 h-5 shrink-0\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n <span class=\"text-sm font-medium\">{{ error }}</span>\r\n </div>\r\n}\r\n\r\n<!-- Loaded State -->\r\n@if (state.isLoaded() && !state.loading()) {\r\n <!-- Step Info Bar -->\r\n @if (state.stepName() || state.mode()) {\r\n <div\r\n class=\"flex items-center gap-3 mb-4 p-3 rounded-lg bg-surface-50 border border-surface-200\"\r\n >\r\n @if (state.stepName()) {\r\n <span class=\"text-sm font-semibold text-surface-700\">\r\n {{ state.stepName() }}\r\n </span>\r\n }\r\n\r\n @if (state.mode()) {\r\n <span\r\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium\"\r\n [class]=\"\r\n state.isApproval()\r\n ? 'bg-amber-100 text-amber-800'\r\n : 'bg-emerald-100 text-emerald-800'\r\n \"\r\n >\r\n {{ state.mode() }}\r\n </span>\r\n }\r\n\r\n @if (state.isFallbackForm()) {\r\n <span\r\n class=\"inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-sky-100 text-sky-800\"\r\n >\r\n {{ state.formSource() }}\r\n </span>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Virtual Fields (read-only process context) -->\r\n @if (hasVirtualFields()) {\r\n <div class=\"mb-4 p-3 rounded-lg bg-surface-50 border border-surface-200\">\r\n <div\r\n class=\"grid grid-cols-2 gap-x-6 gap-y-2 sm:grid-cols-3 lg:grid-cols-4\"\r\n >\r\n @for (field of virtualFields(); track field.propertyKey) {\r\n <div class=\"flex flex-col gap-0.5\">\r\n <span class=\"text-xs text-muted-color font-medium\">\r\n {{ field.propertyKey | titlecase }}\r\n </span>\r\n <span class=\"text-sm text-surface-800 font-medium\">\r\n {{ field.value ?? \"\u2014\" }}\r\n </span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Dynamic Form -->\r\n @if (state.requiresForm() && formConfig(); as config) {\r\n <mt-dynamic-form [formConfig]=\"config\" [formControl]=\"formControl\" />\r\n } @else if (!state.requiresForm()) {\r\n <div\r\n class=\"flex items-center justify-center p-6 rounded-lg bg-surface-50 border border-surface-200 border-dashed\"\r\n >\r\n <p class=\"text-sm text-muted-color\">\r\n No form required for this operation.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!-- Submit Error -->\r\n @if (state.submitError(); as submitError) {\r\n <div\r\n class=\"mt-4 flex items-center gap-2 p-3 rounded-lg bg-red-50 text-red-700 border border-red-200\"\r\n role=\"alert\"\r\n >\r\n <svg class=\"w-5 h-5 shrink-0\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n <span class=\"text-sm font-medium\">{{ submitError }}</span>\r\n </div>\r\n }\r\n\r\n <!-- Submit Success -->\r\n @if (state.isSubmitted()) {\r\n <div\r\n class=\"mt-4 flex items-center gap-2 p-3 rounded-lg border\"\r\n [class]=\"\r\n state.isPendingApproval()\r\n ? 'bg-amber-50 text-amber-700 border-amber-200'\r\n : 'bg-emerald-50 text-emerald-700 border-emerald-200'\r\n \"\r\n >\r\n <svg class=\"w-5 h-5 shrink-0\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path\r\n fill-rule=\"evenodd\"\r\n d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n <span class=\"text-sm font-medium\">\r\n @if (state.isPendingApproval()) {\r\n Request submitted for approval.\r\n } @else {\r\n Operation executed successfully.\r\n }\r\n </span>\r\n </div>\r\n }\r\n\r\n <!-- Submitting Overlay -->\r\n @if (state.submitting()) {\r\n <div class=\"mt-4 flex items-center gap-2 text-sm text-muted-color\">\r\n <div\r\n class=\"w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin\"\r\n ></div>\r\n <span>Submitting...</span>\r\n </div>\r\n }\r\n}\r\n", styles: [":host{display:block}\n"] }]
577
+ }], propDecorators: { moduleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleKey", required: true }] }], operationKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "operationKey", required: true }] }], moduleId: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleId", required: false }] }], levelId: [{ type: i0.Input, args: [{ isSignal: true, alias: "levelId", required: false }] }], levelDataId: [{ type: i0.Input, args: [{ isSignal: true, alias: "levelDataId", required: false }] }], moduleDataId: [{ type: i0.Input, args: [{ isSignal: true, alias: "moduleDataId", required: false }] }], requestSchemaId: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestSchemaId", required: false }] }], draftProcessId: [{ type: i0.Input, args: [{ isSignal: true, alias: "draftProcessId", required: false }] }], preview: [{ type: i0.Input, args: [{ isSignal: true, alias: "preview", required: false }] }], returnUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnUrl", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], autoLoad: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoLoad", required: false }] }], formMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "formMode", required: false }] }], lang: [{ type: i0.Input, args: [{ isSignal: true, alias: "lang", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], submitted: [{ type: i0.Output, args: ["submitted"] }], errored: [{ type: i0.Output, args: ["errored"] }], modeDetected: [{ type: i0.Output, args: ["modeDetected"] }], formSourceDetected: [{ type: i0.Output, args: ["formSourceDetected"] }] } });
578
+
579
+ // ============================================================================
580
+ // API Response Wrapper
581
+ // ============================================================================
582
+ /**
583
+ * Type guard to detect a FormRequired interception response from legacy commands.
584
+ * Use in HTTP interceptors to redirect to process-forms flow.
585
+ */
586
+ function isFormRequiredInterception(response) {
587
+ return (response?.status === 'FormRequired' &&
588
+ typeof response?.requestSchemaId === 'number');
589
+ }
590
+
591
+ // Client Form - Runtime process form component
592
+
593
+ /**
594
+ * Generated bundle index. Do not edit.
595
+ */
596
+
597
+ export { ClientForm, ClientFormApiService, ClientFormStateService, isFormRequiredInterception, mapFormValueToSubmitValues, mapToDynamicFormConfig, mapValuesToFormValue };
598
+ //# sourceMappingURL=masterteam-forms-client-form.mjs.map