@masterteam/forms 0.0.35 → 0.0.37
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/assets/forms.css +2 -2
- package/fesm2022/masterteam-forms-client-form.mjs +690 -0
- package/fesm2022/masterteam-forms-client-form.mjs.map +1 -0
- package/fesm2022/masterteam-forms-dynamic-field.mjs +1 -1
- package/fesm2022/masterteam-forms-dynamic-field.mjs.map +1 -1
- package/package.json +6 -2
- package/types/masterteam-forms-client-form.d.ts +380 -0
|
@@ -0,0 +1,690 @@
|
|
|
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, HttpContext } from '@angular/common/http';
|
|
9
|
+
import { ValidatorConfig, TextFieldConfig, SelectFieldConfig, MultiSelectFieldConfig, UserSearchFieldConfig, REQUEST_CONTEXT, UploadFileFieldConfig, ToggleFieldConfig, DateFieldConfig, SliderFieldConfig, NumberFieldConfig, EditorFieldConfig } from '@masterteam/components';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Stateless HTTP service for process-forms runtime APIs.
|
|
13
|
+
* Root-provided — safe to share across multiple ClientForm instances.
|
|
14
|
+
*/
|
|
15
|
+
class ClientFormApiService {
|
|
16
|
+
http = inject(HttpClient);
|
|
17
|
+
baseUrl = 'process-forms';
|
|
18
|
+
/**
|
|
19
|
+
* Load form configuration and values for a given operation context.
|
|
20
|
+
* Backend determines mode (Approval vs Direct) based on published schema.
|
|
21
|
+
*/
|
|
22
|
+
load(request) {
|
|
23
|
+
return this.http.post(`${this.baseUrl}/load`, request);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Submit form values. Result depends on mode:
|
|
27
|
+
* - Approval → status: 'PendingApproval'
|
|
28
|
+
* - Direct → status: 'Executed'
|
|
29
|
+
*/
|
|
30
|
+
submit(request) {
|
|
31
|
+
return this.http.post(`${this.baseUrl}/submit`, request);
|
|
32
|
+
}
|
|
33
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
34
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormApiService, providedIn: 'root' });
|
|
35
|
+
}
|
|
36
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormApiService, decorators: [{
|
|
37
|
+
type: Injectable,
|
|
38
|
+
args: [{ providedIn: 'root' }]
|
|
39
|
+
}] });
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Per-instance signal-based state for ClientForm.
|
|
43
|
+
*
|
|
44
|
+
* NOT providedIn root — each ClientForm component provides its own instance
|
|
45
|
+
* via `providers: [ClientFormStateService]`, enabling multiple independent
|
|
46
|
+
* forms on the same page.
|
|
47
|
+
*/
|
|
48
|
+
class ClientFormStateService {
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Core State Signals
|
|
51
|
+
// ============================================================================
|
|
52
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
53
|
+
submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
|
|
54
|
+
error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
|
|
55
|
+
submitError = signal(null, ...(ngDevMode ? [{ debugName: "submitError" }] : []));
|
|
56
|
+
loadResponse = signal(null, ...(ngDevMode ? [{ debugName: "loadResponse" }] : []));
|
|
57
|
+
submitResponse = signal(null, ...(ngDevMode ? [{ debugName: "submitResponse" }] : []));
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Derived Computeds — Load Response
|
|
60
|
+
// ============================================================================
|
|
61
|
+
isLoaded = computed(() => !!this.loadResponse(), ...(ngDevMode ? [{ debugName: "isLoaded" }] : []));
|
|
62
|
+
mode = computed(() => this.loadResponse()?.mode ?? null, ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
63
|
+
isApproval = computed(() => this.mode() === 'Approval', ...(ngDevMode ? [{ debugName: "isApproval" }] : []));
|
|
64
|
+
isDirect = computed(() => this.mode() === 'Direct', ...(ngDevMode ? [{ debugName: "isDirect" }] : []));
|
|
65
|
+
formSource = computed(() => this.loadResponse()?.formSource ?? null, ...(ngDevMode ? [{ debugName: "formSource" }] : []));
|
|
66
|
+
isFallbackForm = computed(() => {
|
|
67
|
+
const source = this.formSource();
|
|
68
|
+
return source === 'ModuleFallback' || source === 'LevelFallback';
|
|
69
|
+
}, ...(ngDevMode ? [{ debugName: "isFallbackForm" }] : []));
|
|
70
|
+
requiresForm = computed(() => this.loadResponse()?.requiresForm ?? false, ...(ngDevMode ? [{ debugName: "requiresForm" }] : []));
|
|
71
|
+
formConfiguration = computed(() => this.loadResponse()?.formConfiguration ?? null, ...(ngDevMode ? [{ debugName: "formConfiguration" }] : []));
|
|
72
|
+
values = computed(() => this.loadResponse()?.values ?? [], ...(ngDevMode ? [{ debugName: "values" }] : []));
|
|
73
|
+
context = computed(() => this.loadResponse()?.context ?? null, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
74
|
+
stepName = computed(() => this.loadResponse()?.stepName ?? null, ...(ngDevMode ? [{ debugName: "stepName" }] : []));
|
|
75
|
+
requestSchemaId = computed(() => this.loadResponse()?.requestSchemaId ?? null, ...(ngDevMode ? [{ debugName: "requestSchemaId" }] : []));
|
|
76
|
+
requestId = computed(() => this.loadResponse()?.requestId ?? null, ...(ngDevMode ? [{ debugName: "requestId" }] : []));
|
|
77
|
+
stepId = computed(() => this.loadResponse()?.stepId ?? null, ...(ngDevMode ? [{ debugName: "stepId" }] : []));
|
|
78
|
+
stepSchemaId = computed(() => this.loadResponse()?.stepSchemaId ?? null, ...(ngDevMode ? [{ debugName: "stepSchemaId" }] : []));
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Derived Computeds — Value Categories
|
|
81
|
+
// ============================================================================
|
|
82
|
+
/** Process virtual fields (Request_Date, Step_Name, etc.) — read-only display */
|
|
83
|
+
virtualFields = computed(() => this.values().filter((v) => v.metadata?.source === 'ProcessVirtual'), ...(ngDevMode ? [{ debugName: "virtualFields" }] : []));
|
|
84
|
+
/** Editable form values (non-virtual) */
|
|
85
|
+
formValues = computed(() => this.values().filter((v) => v.metadata?.source !== 'ProcessVirtual'), ...(ngDevMode ? [{ debugName: "formValues" }] : []));
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Derived Computeds — Submit Response
|
|
88
|
+
// ============================================================================
|
|
89
|
+
isSubmitted = computed(() => !!this.submitResponse(), ...(ngDevMode ? [{ debugName: "isSubmitted" }] : []));
|
|
90
|
+
submitStatus = computed(() => this.submitResponse()?.status ?? null, ...(ngDevMode ? [{ debugName: "submitStatus" }] : []));
|
|
91
|
+
isPendingApproval = computed(() => this.submitStatus() === 'PendingApproval', ...(ngDevMode ? [{ debugName: "isPendingApproval" }] : []));
|
|
92
|
+
isExecuted = computed(() => this.submitStatus() === 'Executed', ...(ngDevMode ? [{ debugName: "isExecuted" }] : []));
|
|
93
|
+
createdEntityId = computed(() => this.submitResponse()?.createdEntityId ?? null, ...(ngDevMode ? [{ debugName: "createdEntityId" }] : []));
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// State Mutations
|
|
96
|
+
// ============================================================================
|
|
97
|
+
setLoadResponse(response) {
|
|
98
|
+
this.loadResponse.set(response);
|
|
99
|
+
this.error.set(null);
|
|
100
|
+
}
|
|
101
|
+
setSubmitResponse(response) {
|
|
102
|
+
this.submitResponse.set(response);
|
|
103
|
+
this.submitError.set(null);
|
|
104
|
+
}
|
|
105
|
+
setError(message) {
|
|
106
|
+
this.error.set(message);
|
|
107
|
+
this.loading.set(false);
|
|
108
|
+
}
|
|
109
|
+
setSubmitError(message) {
|
|
110
|
+
this.submitError.set(message);
|
|
111
|
+
this.submitting.set(false);
|
|
112
|
+
}
|
|
113
|
+
reset() {
|
|
114
|
+
this.loading.set(false);
|
|
115
|
+
this.submitting.set(false);
|
|
116
|
+
this.error.set(null);
|
|
117
|
+
this.submitError.set(null);
|
|
118
|
+
this.loadResponse.set(null);
|
|
119
|
+
this.submitResponse.set(null);
|
|
120
|
+
}
|
|
121
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormStateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
122
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormStateService });
|
|
123
|
+
}
|
|
124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormStateService, decorators: [{
|
|
125
|
+
type: Injectable
|
|
126
|
+
}] });
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Constants
|
|
130
|
+
// ============================================================================
|
|
131
|
+
const WIDTH_TO_COLSPAN = {
|
|
132
|
+
'25': 3,
|
|
133
|
+
'50': 6,
|
|
134
|
+
'100': 12,
|
|
135
|
+
};
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Public Mapper Functions
|
|
138
|
+
// ============================================================================
|
|
139
|
+
/**
|
|
140
|
+
* Convert a runtime FormConfiguration into a DynamicFormConfig
|
|
141
|
+
* that can be passed directly to `<mt-dynamic-form>`.
|
|
142
|
+
*
|
|
143
|
+
* @param config The form configuration from the load API
|
|
144
|
+
* @param lang Current UI language ('en' | 'ar')
|
|
145
|
+
* @param mode 'create' or 'edit' — filters hidden fields accordingly
|
|
146
|
+
* @param lookups Available lookup definitions for resolving Lookup/LookupMultiSelect options
|
|
147
|
+
*/
|
|
148
|
+
function mapToDynamicFormConfig(config, lang = 'en', mode = 'create', lookups = []) {
|
|
149
|
+
return {
|
|
150
|
+
sections: config.sections
|
|
151
|
+
.slice()
|
|
152
|
+
.sort((a, b) => a.order - b.order)
|
|
153
|
+
.map((section) => {
|
|
154
|
+
const sectionName = section.name[lang] ?? section.name['en'] ?? '';
|
|
155
|
+
const visibleFields = section.fields
|
|
156
|
+
.filter((field) => {
|
|
157
|
+
// isRead=false → completely hidden
|
|
158
|
+
if (field.isRead === false)
|
|
159
|
+
return false;
|
|
160
|
+
if (mode === 'create')
|
|
161
|
+
return !field.hiddenInCreation;
|
|
162
|
+
return !field.hiddenInEditForm;
|
|
163
|
+
})
|
|
164
|
+
.sort((a, b) => a.order - b.order);
|
|
165
|
+
return {
|
|
166
|
+
key: section.id,
|
|
167
|
+
label: sectionName,
|
|
168
|
+
type: 'header',
|
|
169
|
+
columns: 12,
|
|
170
|
+
order: section.order,
|
|
171
|
+
fields: visibleFields.map((field) => mapFieldToConfig(field, lang, lookups)),
|
|
172
|
+
};
|
|
173
|
+
})
|
|
174
|
+
.filter((section) => section.fields.length > 0),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Convert API property values into a flat key-value object
|
|
179
|
+
* suitable for `formControl.patchValue()`.
|
|
180
|
+
*
|
|
181
|
+
* Only includes non-virtual (editable) values.
|
|
182
|
+
*/
|
|
183
|
+
function mapValuesToFormValue(values) {
|
|
184
|
+
const result = {};
|
|
185
|
+
for (const v of values) {
|
|
186
|
+
if (v.metadata?.source === 'ProcessVirtual')
|
|
187
|
+
continue;
|
|
188
|
+
result[v.propertyKey] = v.value;
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Convert the current form value back into the submit payload format.
|
|
194
|
+
*
|
|
195
|
+
* Maps `requestPropertyId` from the load response metadata where available,
|
|
196
|
+
* so backend can match to schema request properties.
|
|
197
|
+
*/
|
|
198
|
+
function mapFormValueToSubmitValues(formValue, loadResponse) {
|
|
199
|
+
const metadataByKey = new Map();
|
|
200
|
+
for (const v of loadResponse.values) {
|
|
201
|
+
if (v.metadata) {
|
|
202
|
+
metadataByKey.set(v.propertyKey, {
|
|
203
|
+
propertyId: v.metadata.propertyId,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return Object.entries(formValue)
|
|
208
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
209
|
+
.map(([propertyKey, value]) => {
|
|
210
|
+
const meta = metadataByKey.get(propertyKey);
|
|
211
|
+
const submitValue = { propertyKey, value };
|
|
212
|
+
if (meta?.propertyId) {
|
|
213
|
+
submitValue.requestPropertyId = meta.propertyId;
|
|
214
|
+
}
|
|
215
|
+
return submitValue;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// Internal Helpers
|
|
220
|
+
// ============================================================================
|
|
221
|
+
/**
|
|
222
|
+
* Resolve the property item from either `property` or `propertyMetadata`.
|
|
223
|
+
* The API may return the data under either key.
|
|
224
|
+
*/
|
|
225
|
+
function resolveProperty(field) {
|
|
226
|
+
return field.property ?? field.propertyMetadata;
|
|
227
|
+
}
|
|
228
|
+
function mapFieldToConfig(field, lang, lookups) {
|
|
229
|
+
const prop = resolveProperty(field);
|
|
230
|
+
const viewType = prop?.viewType ?? 'Text';
|
|
231
|
+
const label = resolvePropertyName(prop, lang) || field.propertyKey;
|
|
232
|
+
const colSpan = WIDTH_TO_COLSPAN[field.width] ?? 12;
|
|
233
|
+
const base = {
|
|
234
|
+
key: field.propertyKey,
|
|
235
|
+
label,
|
|
236
|
+
colSpan,
|
|
237
|
+
order: field.order,
|
|
238
|
+
placeholder: label,
|
|
239
|
+
required: field.isRequired ?? false,
|
|
240
|
+
readonly: field.isWrite === false,
|
|
241
|
+
validators: field.isRequired
|
|
242
|
+
? [ValidatorConfig.required(`${label} is required`)]
|
|
243
|
+
: [],
|
|
244
|
+
};
|
|
245
|
+
switch (viewType) {
|
|
246
|
+
// ── Text-like ──────────────────────────────────────────────
|
|
247
|
+
case 'Text':
|
|
248
|
+
case 'Currency':
|
|
249
|
+
case 'EditableListView':
|
|
250
|
+
case 'LookupLog':
|
|
251
|
+
return new TextFieldConfig(base);
|
|
252
|
+
case 'LongText':
|
|
253
|
+
return new EditorFieldConfig(base);
|
|
254
|
+
// ── Numeric ───────────────────────────────────────────────
|
|
255
|
+
case 'Number':
|
|
256
|
+
return new NumberFieldConfig(base);
|
|
257
|
+
case 'Percentage':
|
|
258
|
+
return new SliderFieldConfig({ ...base, min: 0, max: 100 });
|
|
259
|
+
// ── Date / Time ───────────────────────────────────────────
|
|
260
|
+
case 'Date':
|
|
261
|
+
return new DateFieldConfig({ ...base, showTime: false });
|
|
262
|
+
case 'DateTime':
|
|
263
|
+
return new DateFieldConfig({ ...base, showTime: true });
|
|
264
|
+
case 'Time':
|
|
265
|
+
return new DateFieldConfig({ ...base, showTime: true });
|
|
266
|
+
// ── Boolean ───────────────────────────────────────────────
|
|
267
|
+
case 'Checkbox':
|
|
268
|
+
return new ToggleFieldConfig(base);
|
|
269
|
+
// ── File ──────────────────────────────────────────────────
|
|
270
|
+
case 'Attachment':
|
|
271
|
+
return new UploadFileFieldConfig(base);
|
|
272
|
+
// ── User Search ───────────────────────────────────────────
|
|
273
|
+
case 'User':
|
|
274
|
+
return new UserSearchFieldConfig({
|
|
275
|
+
...base,
|
|
276
|
+
apiUrl: 'Identity/users',
|
|
277
|
+
context: new HttpContext().set(REQUEST_CONTEXT, {
|
|
278
|
+
useBaseUrl: false,
|
|
279
|
+
}),
|
|
280
|
+
});
|
|
281
|
+
// ── Lookup (single select) ────────────────────────────────
|
|
282
|
+
case 'Lookup': {
|
|
283
|
+
const items = resolveLookupOptions(prop, lookups);
|
|
284
|
+
return new SelectFieldConfig({
|
|
285
|
+
...base,
|
|
286
|
+
options: items,
|
|
287
|
+
optionLabel: 'label',
|
|
288
|
+
optionValue: 'value',
|
|
289
|
+
filter: items.length > 10,
|
|
290
|
+
showClear: !(field.isRequired ?? false),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
// ── Lookup (multi select) ─────────────────────────────────
|
|
294
|
+
case 'LookupMultiSelect': {
|
|
295
|
+
const items = resolveLookupOptions(prop, lookups);
|
|
296
|
+
return new MultiSelectFieldConfig({
|
|
297
|
+
...base,
|
|
298
|
+
options: items,
|
|
299
|
+
optionLabel: 'label',
|
|
300
|
+
optionValue: 'value',
|
|
301
|
+
filter: items.length > 10,
|
|
302
|
+
display: 'chip',
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// ── Other select-based types ──────────────────────────────
|
|
306
|
+
case 'Status':
|
|
307
|
+
case 'InternalModule':
|
|
308
|
+
case 'DynamicList':
|
|
309
|
+
case 'API':
|
|
310
|
+
case 'LookupMatrix':
|
|
311
|
+
case 'Location': {
|
|
312
|
+
const options = extractOptionsFromProperty(prop);
|
|
313
|
+
return new SelectFieldConfig({
|
|
314
|
+
...base,
|
|
315
|
+
options: options ?? [],
|
|
316
|
+
optionLabel: 'label',
|
|
317
|
+
optionValue: 'value',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// ── Fallback ──────────────────────────────────────────────
|
|
321
|
+
default:
|
|
322
|
+
return new TextFieldConfig(base);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function resolvePropertyName(property, lang) {
|
|
326
|
+
if (!property?.name)
|
|
327
|
+
return '';
|
|
328
|
+
if (typeof property.name === 'string')
|
|
329
|
+
return property.name;
|
|
330
|
+
// Prefer display name, then lang-specific, then English fallback
|
|
331
|
+
return property.name['display'] ?? property.name[lang] ?? property.name['en'] ?? '';
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Resolve lookup items for Lookup / LookupMultiSelect viewTypes.
|
|
335
|
+
*
|
|
336
|
+
* Reads `configuration.lookup` (the lookup ID) from the property metadata,
|
|
337
|
+
* finds the matching lookup definition, and maps its items to select options.
|
|
338
|
+
*/
|
|
339
|
+
function resolveLookupOptions(prop, lookups) {
|
|
340
|
+
const lookupId = prop?.configuration?.['lookup'];
|
|
341
|
+
if (!lookupId || !lookups.length)
|
|
342
|
+
return [];
|
|
343
|
+
const lookup = lookups.find((l) => l.id === lookupId);
|
|
344
|
+
if (!lookup)
|
|
345
|
+
return [];
|
|
346
|
+
return lookup.items
|
|
347
|
+
.slice()
|
|
348
|
+
.sort((a, b) => a.order - b.order)
|
|
349
|
+
.map((item) => ({
|
|
350
|
+
label: item.name?.display ?? item.key,
|
|
351
|
+
value: item.id,
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Fallback option extractor for non-lookup select types
|
|
356
|
+
* (Status, InternalModule, DynamicList, API, etc.).
|
|
357
|
+
*/
|
|
358
|
+
function extractOptionsFromProperty(property) {
|
|
359
|
+
if (!property?.configuration)
|
|
360
|
+
return null;
|
|
361
|
+
const config = property.configuration;
|
|
362
|
+
if (Array.isArray(config['options'])) {
|
|
363
|
+
return config['options'];
|
|
364
|
+
}
|
|
365
|
+
if (Array.isArray(config['items'])) {
|
|
366
|
+
return config['items'].map((item) => ({
|
|
367
|
+
label: typeof item.name === 'string'
|
|
368
|
+
? item.name
|
|
369
|
+
: (item.name?.['en'] ?? item.label ?? String(item.value)),
|
|
370
|
+
value: item.id ?? item.value ?? item.key,
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Client Form — Runtime process form component.
|
|
378
|
+
*
|
|
379
|
+
* Self-contained, signal-based (no NGXS). Each instance manages its own state
|
|
380
|
+
* via a component-scoped `ClientFormStateService`.
|
|
381
|
+
*
|
|
382
|
+
* **No action buttons in template.** Parent controls all actions via `viewChild()`:
|
|
383
|
+
*
|
|
384
|
+
* ```html
|
|
385
|
+
* <mt-client-form #processForm [moduleKey]="'Risk'" [operationKey]="'CloseRisk'" />
|
|
386
|
+
* <button (click)="processForm.load()">Load</button>
|
|
387
|
+
* <button (click)="processForm.submit()">Submit</button>
|
|
388
|
+
* ```
|
|
389
|
+
*
|
|
390
|
+
* Or programmatically:
|
|
391
|
+
* ```typescript
|
|
392
|
+
* readonly processForm = viewChild.required(ClientForm);
|
|
393
|
+
* this.processForm().load();
|
|
394
|
+
* this.processForm().submit();
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
class ClientForm {
|
|
398
|
+
api = inject(ClientFormApiService);
|
|
399
|
+
state = inject(ClientFormStateService);
|
|
400
|
+
loadSub;
|
|
401
|
+
submitSub;
|
|
402
|
+
// ============================================================================
|
|
403
|
+
// Public State Signals (for parent access via viewChild)
|
|
404
|
+
// ============================================================================
|
|
405
|
+
submitting = computed(() => this.state.submitting(), ...(ngDevMode ? [{ debugName: "submitting" }] : []));
|
|
406
|
+
submitError = computed(() => this.state.submitError(), ...(ngDevMode ? [{ debugName: "submitError" }] : []));
|
|
407
|
+
isSubmitted = computed(() => this.state.isSubmitted(), ...(ngDevMode ? [{ debugName: "isSubmitted" }] : []));
|
|
408
|
+
isPendingApproval = computed(() => this.state.isPendingApproval(), ...(ngDevMode ? [{ debugName: "isPendingApproval" }] : []));
|
|
409
|
+
isExecuted = computed(() => this.state.isExecuted(), ...(ngDevMode ? [{ debugName: "isExecuted" }] : []));
|
|
410
|
+
isLoaded = computed(() => this.state.isLoaded(), ...(ngDevMode ? [{ debugName: "isLoaded" }] : []));
|
|
411
|
+
loading = computed(() => this.state.loading(), ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
412
|
+
// ============================================================================
|
|
413
|
+
// Inputs — Required Context
|
|
414
|
+
// ============================================================================
|
|
415
|
+
moduleKey = input.required(...(ngDevMode ? [{ debugName: "moduleKey" }] : []));
|
|
416
|
+
operationKey = input.required(...(ngDevMode ? [{ debugName: "operationKey" }] : []));
|
|
417
|
+
// ============================================================================
|
|
418
|
+
// Inputs — Optional Context
|
|
419
|
+
// ============================================================================
|
|
420
|
+
moduleId = input(...(ngDevMode ? [undefined, { debugName: "moduleId" }] : []));
|
|
421
|
+
levelId = input(...(ngDevMode ? [undefined, { debugName: "levelId" }] : []));
|
|
422
|
+
levelDataId = input(...(ngDevMode ? [undefined, { debugName: "levelDataId" }] : []));
|
|
423
|
+
moduleDataId = input(...(ngDevMode ? [undefined, { debugName: "moduleDataId" }] : []));
|
|
424
|
+
requestSchemaId = input(...(ngDevMode ? [undefined, { debugName: "requestSchemaId" }] : []));
|
|
425
|
+
draftProcessId = input(...(ngDevMode ? [undefined, { debugName: "draftProcessId" }] : []));
|
|
426
|
+
preview = input(false, ...(ngDevMode ? [{ debugName: "preview" }] : []));
|
|
427
|
+
returnUrl = input(...(ngDevMode ? [undefined, { debugName: "returnUrl" }] : []));
|
|
428
|
+
// ============================================================================
|
|
429
|
+
// Inputs — UI Configuration
|
|
430
|
+
// ============================================================================
|
|
431
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : []));
|
|
432
|
+
autoLoad = input(true, ...(ngDevMode ? [{ debugName: "autoLoad" }] : []));
|
|
433
|
+
formMode = input('create', ...(ngDevMode ? [{ debugName: "formMode" }] : []));
|
|
434
|
+
lang = input('en', ...(ngDevMode ? [{ debugName: "lang" }] : []));
|
|
435
|
+
lookups = input([], ...(ngDevMode ? [{ debugName: "lookups" }] : []));
|
|
436
|
+
// ============================================================================
|
|
437
|
+
// Outputs
|
|
438
|
+
// ============================================================================
|
|
439
|
+
loaded = output();
|
|
440
|
+
submitted = output();
|
|
441
|
+
errored = output();
|
|
442
|
+
modeDetected = output();
|
|
443
|
+
formSourceDetected = output();
|
|
444
|
+
// ============================================================================
|
|
445
|
+
// Internal Form Control
|
|
446
|
+
// ============================================================================
|
|
447
|
+
formControl = new FormControl({});
|
|
448
|
+
// ============================================================================
|
|
449
|
+
// Computed — Dynamic Form Config
|
|
450
|
+
// ============================================================================
|
|
451
|
+
formConfig = computed(() => {
|
|
452
|
+
const config = this.state.formConfiguration();
|
|
453
|
+
if (!config)
|
|
454
|
+
return null;
|
|
455
|
+
return mapToDynamicFormConfig(config, this.lang(), this.formMode(), this.lookups());
|
|
456
|
+
}, ...(ngDevMode ? [{ debugName: "formConfig" }] : []));
|
|
457
|
+
initialValues = computed(() => {
|
|
458
|
+
return mapValuesToFormValue(this.state.formValues());
|
|
459
|
+
}, ...(ngDevMode ? [{ debugName: "initialValues" }] : []));
|
|
460
|
+
virtualFields = computed(() => this.state.virtualFields(), ...(ngDevMode ? [{ debugName: "virtualFields" }] : []));
|
|
461
|
+
hasVirtualFields = computed(() => this.virtualFields().length > 0, ...(ngDevMode ? [{ debugName: "hasVirtualFields" }] : []));
|
|
462
|
+
// ============================================================================
|
|
463
|
+
// Effects
|
|
464
|
+
// ============================================================================
|
|
465
|
+
constructor() {
|
|
466
|
+
// Auto-load when inputs are ready
|
|
467
|
+
effect(() => {
|
|
468
|
+
const autoLoad = this.autoLoad();
|
|
469
|
+
const moduleKey = this.moduleKey();
|
|
470
|
+
const operationKey = this.operationKey();
|
|
471
|
+
if (autoLoad && moduleKey && operationKey) {
|
|
472
|
+
untracked(() => this.load());
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
// Patch form values after load
|
|
476
|
+
effect(() => {
|
|
477
|
+
const values = this.initialValues();
|
|
478
|
+
const isLoaded = this.state.isLoaded();
|
|
479
|
+
if (isLoaded && Object.keys(values).length > 0) {
|
|
480
|
+
untracked(() => {
|
|
481
|
+
this.formControl.patchValue(values, { emitEvent: false });
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
// ============================================================================
|
|
487
|
+
// Public API (accessed via viewChild)
|
|
488
|
+
// ============================================================================
|
|
489
|
+
/**
|
|
490
|
+
* Load form configuration from the API.
|
|
491
|
+
* Builds request from current input values.
|
|
492
|
+
*/
|
|
493
|
+
load() {
|
|
494
|
+
if (this.state.loading())
|
|
495
|
+
return;
|
|
496
|
+
this.loadSub?.unsubscribe();
|
|
497
|
+
this.state.loading.set(true);
|
|
498
|
+
this.state.error.set(null);
|
|
499
|
+
this.state.submitResponse.set(null);
|
|
500
|
+
const request = this.buildLoadRequest();
|
|
501
|
+
this.loadSub = this.api.load(request).subscribe({
|
|
502
|
+
next: (response) => {
|
|
503
|
+
this.state.loading.set(false);
|
|
504
|
+
if (response.data) {
|
|
505
|
+
this.state.setLoadResponse(response.data);
|
|
506
|
+
this.loaded.emit(response.data);
|
|
507
|
+
if (response.data.mode) {
|
|
508
|
+
this.modeDetected.emit(response.data.mode);
|
|
509
|
+
}
|
|
510
|
+
if (response.data.formSource) {
|
|
511
|
+
this.formSourceDetected.emit(response.data.formSource);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
const msg = response.message ?? 'Failed to load form';
|
|
516
|
+
this.state.setError(msg);
|
|
517
|
+
this.errored.emit(msg);
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
error: (err) => {
|
|
521
|
+
const msg = err?.error?.message ?? err?.message ?? 'Failed to load form';
|
|
522
|
+
this.state.setError(msg);
|
|
523
|
+
this.errored.emit(msg);
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Submit the current form values.
|
|
529
|
+
* Builds submit request from form value + load context.
|
|
530
|
+
*/
|
|
531
|
+
submit() {
|
|
532
|
+
if (this.state.submitting())
|
|
533
|
+
return;
|
|
534
|
+
this.submitSub?.unsubscribe();
|
|
535
|
+
this.state.submitting.set(true);
|
|
536
|
+
this.state.submitError.set(null);
|
|
537
|
+
const request = this.buildSubmitRequest();
|
|
538
|
+
this.submitSub = this.api.submit(request).subscribe({
|
|
539
|
+
next: (response) => {
|
|
540
|
+
this.state.submitting.set(false);
|
|
541
|
+
if (response.data) {
|
|
542
|
+
this.state.setSubmitResponse(response.data);
|
|
543
|
+
this.submitted.emit(response.data);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
const msg = response.message ?? 'Failed to submit form';
|
|
547
|
+
this.state.setSubmitError(msg);
|
|
548
|
+
this.errored.emit(msg);
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
error: (err) => {
|
|
552
|
+
const msg = err?.error?.message ?? err?.message ?? 'Failed to submit form';
|
|
553
|
+
this.state.setSubmitError(msg);
|
|
554
|
+
this.errored.emit(msg);
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Get the current form value as a flat key-value object.
|
|
560
|
+
*/
|
|
561
|
+
getFormValue() {
|
|
562
|
+
return this.formControl.value ?? {};
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Get the current form value mapped to submit payload format.
|
|
566
|
+
*/
|
|
567
|
+
getSubmitValues() {
|
|
568
|
+
const loadResponse = this.state.loadResponse();
|
|
569
|
+
if (!loadResponse)
|
|
570
|
+
return [];
|
|
571
|
+
return mapFormValueToSubmitValues(this.getFormValue(), loadResponse);
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Check whether the current form state is valid.
|
|
575
|
+
*/
|
|
576
|
+
isValid() {
|
|
577
|
+
return this.formControl.valid;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Reset the component to its initial state.
|
|
581
|
+
*/
|
|
582
|
+
reset() {
|
|
583
|
+
this.loadSub?.unsubscribe();
|
|
584
|
+
this.submitSub?.unsubscribe();
|
|
585
|
+
this.formControl.reset({});
|
|
586
|
+
this.state.reset();
|
|
587
|
+
}
|
|
588
|
+
// ============================================================================
|
|
589
|
+
// Lifecycle
|
|
590
|
+
// ============================================================================
|
|
591
|
+
ngOnDestroy() {
|
|
592
|
+
this.loadSub?.unsubscribe();
|
|
593
|
+
this.submitSub?.unsubscribe();
|
|
594
|
+
}
|
|
595
|
+
// ============================================================================
|
|
596
|
+
// Private Helpers
|
|
597
|
+
// ============================================================================
|
|
598
|
+
buildLoadRequest() {
|
|
599
|
+
const req = {
|
|
600
|
+
moduleKey: this.moduleKey(),
|
|
601
|
+
operationKey: this.operationKey(),
|
|
602
|
+
};
|
|
603
|
+
const moduleId = this.moduleId();
|
|
604
|
+
const levelId = this.levelId();
|
|
605
|
+
const levelDataId = this.levelDataId();
|
|
606
|
+
const moduleDataId = this.moduleDataId();
|
|
607
|
+
const requestSchemaId = this.requestSchemaId();
|
|
608
|
+
const draftProcessId = this.draftProcessId();
|
|
609
|
+
const preview = this.preview();
|
|
610
|
+
if (moduleId != null)
|
|
611
|
+
req.moduleId = moduleId;
|
|
612
|
+
if (levelId != null)
|
|
613
|
+
req.levelId = levelId;
|
|
614
|
+
if (levelDataId != null)
|
|
615
|
+
req.levelDataId = levelDataId;
|
|
616
|
+
if (moduleDataId != null)
|
|
617
|
+
req.moduleDataId = moduleDataId;
|
|
618
|
+
if (requestSchemaId != null)
|
|
619
|
+
req.requestSchemaId = requestSchemaId;
|
|
620
|
+
if (draftProcessId != null)
|
|
621
|
+
req.draftProcessId = draftProcessId;
|
|
622
|
+
if (preview)
|
|
623
|
+
req.preview = preview;
|
|
624
|
+
return req;
|
|
625
|
+
}
|
|
626
|
+
buildSubmitRequest() {
|
|
627
|
+
const loadResponse = this.state.loadResponse();
|
|
628
|
+
const context = this.state.context();
|
|
629
|
+
const formValue = this.getFormValue();
|
|
630
|
+
const values = loadResponse
|
|
631
|
+
? mapFormValueToSubmitValues(formValue, loadResponse)
|
|
632
|
+
: Object.entries(formValue)
|
|
633
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
634
|
+
.map(([propertyKey, value]) => ({ propertyKey, value }));
|
|
635
|
+
const req = {
|
|
636
|
+
moduleKey: context?.moduleKey ?? this.moduleKey(),
|
|
637
|
+
operationKey: context?.operationKey ?? this.operationKey(),
|
|
638
|
+
values,
|
|
639
|
+
};
|
|
640
|
+
const moduleId = context?.moduleId ?? this.moduleId();
|
|
641
|
+
const levelId = context?.levelId ?? this.levelId();
|
|
642
|
+
const levelDataId = context?.levelDataId ?? this.levelDataId();
|
|
643
|
+
const moduleDataId = context?.moduleDataId ?? this.moduleDataId();
|
|
644
|
+
const requestSchemaId = context?.requestSchemaId ?? this.requestSchemaId();
|
|
645
|
+
const draftProcessId = this.draftProcessId();
|
|
646
|
+
const returnUrl = this.returnUrl();
|
|
647
|
+
if (moduleId != null)
|
|
648
|
+
req.moduleId = moduleId;
|
|
649
|
+
if (levelId != null)
|
|
650
|
+
req.levelId = levelId;
|
|
651
|
+
if (levelDataId != null)
|
|
652
|
+
req.levelDataId = levelDataId;
|
|
653
|
+
if (moduleDataId != null)
|
|
654
|
+
req.moduleDataId = moduleDataId;
|
|
655
|
+
if (requestSchemaId != null)
|
|
656
|
+
req.requestSchemaId = requestSchemaId;
|
|
657
|
+
if (draftProcessId != null)
|
|
658
|
+
req.draftProcessId = draftProcessId;
|
|
659
|
+
if (returnUrl)
|
|
660
|
+
req.returnUrl = returnUrl;
|
|
661
|
+
return req;
|
|
662
|
+
}
|
|
663
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
664
|
+
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 }, lookups: { classPropertyName: "lookups", publicName: "lookups", 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", 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" }] });
|
|
665
|
+
}
|
|
666
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientForm, decorators: [{
|
|
667
|
+
type: Component,
|
|
668
|
+
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", styles: [":host{display:block}\n"] }]
|
|
669
|
+
}], ctorParameters: () => [], 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 }] }], lookups: [{ type: i0.Input, args: [{ isSignal: true, alias: "lookups", 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"] }] } });
|
|
670
|
+
|
|
671
|
+
// ============================================================================
|
|
672
|
+
// API Response Wrapper
|
|
673
|
+
// ============================================================================
|
|
674
|
+
/**
|
|
675
|
+
* Type guard to detect a FormRequired interception response from legacy commands.
|
|
676
|
+
* Use in HTTP interceptors to redirect to process-forms flow.
|
|
677
|
+
*/
|
|
678
|
+
function isFormRequiredInterception(response) {
|
|
679
|
+
return (response?.status === 'FormRequired' &&
|
|
680
|
+
typeof response?.requestSchemaId === 'number');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Client Form - Runtime process form component
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Generated bundle index. Do not edit.
|
|
687
|
+
*/
|
|
688
|
+
|
|
689
|
+
export { ClientForm, ClientFormApiService, ClientFormStateService, isFormRequiredInterception, mapFormValueToSubmitValues, mapToDynamicFormConfig, mapValuesToFormValue };
|
|
690
|
+
//# sourceMappingURL=masterteam-forms-client-form.mjs.map
|