@masterteam/forms 0.0.35 → 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.
- package/assets/forms.css +2 -2
- package/fesm2022/masterteam-forms-client-form.mjs +598 -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 +337 -0
|
@@ -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
|