@masterteam/forms 0.0.59 → 0.0.61
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/fesm2022/masterteam-forms-client-form.mjs +94 -23
- package/fesm2022/masterteam-forms-client-form.mjs.map +1 -1
- package/fesm2022/masterteam-forms-dynamic-field.mjs +2 -2
- package/fesm2022/masterteam-forms-dynamic-field.mjs.map +1 -1
- package/package.json +2 -2
- package/types/masterteam-forms-client-form.d.ts +33 -2
|
@@ -4,6 +4,7 @@ import * as i2 from '@angular/forms';
|
|
|
4
4
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
5
5
|
import * as i1 from '@angular/common';
|
|
6
6
|
import { CommonModule } from '@angular/common';
|
|
7
|
+
import { switchMap, EMPTY, map } from 'rxjs';
|
|
7
8
|
import { Skeleton } from 'primeng/skeleton';
|
|
8
9
|
import { Button } from '@masterteam/components/button';
|
|
9
10
|
import { EntitiesPreview } from '@masterteam/components/entities';
|
|
@@ -35,6 +36,13 @@ class ClientFormApiService {
|
|
|
35
36
|
submit(request) {
|
|
36
37
|
return this.http.post('process-submit', request);
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Validate the exact submit payload against backend-owned form rules
|
|
41
|
+
* before the real submit call.
|
|
42
|
+
*/
|
|
43
|
+
validate(request) {
|
|
44
|
+
return this.http.post('process-submit/validate', request);
|
|
45
|
+
}
|
|
38
46
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
39
47
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: ClientFormApiService, providedIn: 'root' });
|
|
40
48
|
}
|
|
@@ -151,7 +159,6 @@ const WIDTH_TO_ENTITY_SIZE = {
|
|
|
151
159
|
* @param lookups Available lookup definitions for resolving Lookup/LookupMultiSelect options
|
|
152
160
|
*/
|
|
153
161
|
function mapToDynamicFormConfig(config, lang = 'en', mode = 'create', lookups = [], context = null, readonly = false) {
|
|
154
|
-
const validationRules = mapValidationRules(config, lang);
|
|
155
162
|
return {
|
|
156
163
|
sections: config.sections
|
|
157
164
|
.slice()
|
|
@@ -178,7 +185,6 @@ function mapToDynamicFormConfig(config, lang = 'en', mode = 'create', lookups =
|
|
|
178
185
|
};
|
|
179
186
|
})
|
|
180
187
|
.filter((section) => section.fields.length > 0),
|
|
181
|
-
validationRules,
|
|
182
188
|
};
|
|
183
189
|
}
|
|
184
190
|
/**
|
|
@@ -280,6 +286,7 @@ function mapPreviewSectionsToEntities(config, values, lang = 'en', mode = 'creat
|
|
|
280
286
|
configuration: {
|
|
281
287
|
...property.configuration,
|
|
282
288
|
size: WIDTH_TO_ENTITY_SIZE[field.width] ?? 24,
|
|
289
|
+
labelPosition: 'top',
|
|
283
290
|
},
|
|
284
291
|
};
|
|
285
292
|
}),
|
|
@@ -454,19 +461,6 @@ function buildFormulaCondition(field) {
|
|
|
454
461
|
mode: 'auto',
|
|
455
462
|
};
|
|
456
463
|
}
|
|
457
|
-
function mapValidationRules(config, lang) {
|
|
458
|
-
return (config.validations ?? []).map((rule) => ({
|
|
459
|
-
id: rule.id,
|
|
460
|
-
formulaTokens: rule.formulaTokens,
|
|
461
|
-
formulaText: rule.formulaText,
|
|
462
|
-
message: rule.message?.[lang] ??
|
|
463
|
-
rule.message?.en ??
|
|
464
|
-
rule.message?.ar ??
|
|
465
|
-
'Validation rule failed',
|
|
466
|
-
severity: rule.severity,
|
|
467
|
-
enabled: rule.enabled,
|
|
468
|
-
}));
|
|
469
|
-
}
|
|
470
464
|
/**
|
|
471
465
|
* Resolve lookup items for Lookup / LookupMultiSelect viewTypes.
|
|
472
466
|
*
|
|
@@ -717,8 +711,14 @@ class ClientForm {
|
|
|
717
711
|
state = inject(ClientFormStateService);
|
|
718
712
|
loadSub;
|
|
719
713
|
submitSub;
|
|
714
|
+
formValueSub;
|
|
720
715
|
hasStartedLoad = signal(false, ...(ngDevMode ? [{ debugName: "hasStartedLoad" }] : []));
|
|
721
|
-
|
|
716
|
+
formRuntimeMessages = signal([], ...(ngDevMode ? [{ debugName: "formRuntimeMessages" }] : []));
|
|
717
|
+
submitValidationMessages = signal([], ...(ngDevMode ? [{ debugName: "submitValidationMessages" }] : []));
|
|
718
|
+
runtimeMessages = computed(() => [
|
|
719
|
+
...this.formRuntimeMessages(),
|
|
720
|
+
...this.submitValidationMessages(),
|
|
721
|
+
], ...(ngDevMode ? [{ debugName: "runtimeMessages" }] : []));
|
|
722
722
|
translocoService = inject(TranslocoService);
|
|
723
723
|
// ============================================================================
|
|
724
724
|
// Public State Signals (for parent access via viewChild)
|
|
@@ -950,6 +950,11 @@ class ClientForm {
|
|
|
950
950
|
// Effects
|
|
951
951
|
// ============================================================================
|
|
952
952
|
constructor() {
|
|
953
|
+
this.formValueSub = this.formControl.valueChanges.subscribe(() => {
|
|
954
|
+
if (this.submitValidationMessages().length > 0) {
|
|
955
|
+
this.submitValidationMessages.set([]);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
953
958
|
// Auto-load when inputs are ready
|
|
954
959
|
effect(() => {
|
|
955
960
|
const autoLoad = this.autoLoad();
|
|
@@ -1000,7 +1005,8 @@ class ClientForm {
|
|
|
1000
1005
|
return;
|
|
1001
1006
|
this.loadSub?.unsubscribe();
|
|
1002
1007
|
this.hasStartedLoad.set(true);
|
|
1003
|
-
this.
|
|
1008
|
+
this.formRuntimeMessages.set([]);
|
|
1009
|
+
this.submitValidationMessages.set([]);
|
|
1004
1010
|
this.state.loading.set(true);
|
|
1005
1011
|
this.state.error.set(null);
|
|
1006
1012
|
this.state.submitResponse.set(null);
|
|
@@ -1049,8 +1055,30 @@ class ClientForm {
|
|
|
1049
1055
|
this.state.submitting.set(true);
|
|
1050
1056
|
this.state.submitError.set(null);
|
|
1051
1057
|
const request = this.buildSubmitRequest(loadResponse);
|
|
1052
|
-
this.
|
|
1053
|
-
|
|
1058
|
+
this.submitValidationMessages.set([]);
|
|
1059
|
+
this.submitSub = this.api
|
|
1060
|
+
.validate(request)
|
|
1061
|
+
.pipe(switchMap((validationResponse) => {
|
|
1062
|
+
const validation = validationResponse.data;
|
|
1063
|
+
if (!validation) {
|
|
1064
|
+
const msg = validationResponse.message ?? 'Failed to validate form';
|
|
1065
|
+
this.state.submitting.set(false);
|
|
1066
|
+
this.state.setSubmitError(msg);
|
|
1067
|
+
this.errored.emit(msg);
|
|
1068
|
+
return EMPTY;
|
|
1069
|
+
}
|
|
1070
|
+
this.submitValidationMessages.set(this.mapSubmitValidationMessages(validation.results ?? []));
|
|
1071
|
+
if (validation.hasBlockingFailures) {
|
|
1072
|
+
this.state.submitting.set(false);
|
|
1073
|
+
return EMPTY;
|
|
1074
|
+
}
|
|
1075
|
+
return this.api.submit(request).pipe(map((response) => ({
|
|
1076
|
+
kind: 'submit',
|
|
1077
|
+
response,
|
|
1078
|
+
})));
|
|
1079
|
+
}))
|
|
1080
|
+
.subscribe({
|
|
1081
|
+
next: ({ response }) => {
|
|
1054
1082
|
this.state.submitting.set(false);
|
|
1055
1083
|
if (response.data) {
|
|
1056
1084
|
this.state.setSubmitResponse(response.data);
|
|
@@ -1063,7 +1091,9 @@ class ClientForm {
|
|
|
1063
1091
|
}
|
|
1064
1092
|
},
|
|
1065
1093
|
error: (err) => {
|
|
1066
|
-
const msg = err?.error?.message ??
|
|
1094
|
+
const msg = err?.error?.message ??
|
|
1095
|
+
err?.message ??
|
|
1096
|
+
'Failed to validate or submit form';
|
|
1067
1097
|
this.state.setSubmitError(msg);
|
|
1068
1098
|
this.errored.emit(msg);
|
|
1069
1099
|
},
|
|
@@ -1094,7 +1124,7 @@ class ClientForm {
|
|
|
1094
1124
|
* Check whether the current form state is valid.
|
|
1095
1125
|
*/
|
|
1096
1126
|
isValid() {
|
|
1097
|
-
return this.formControl.valid;
|
|
1127
|
+
return this.formControl.valid && this.runtimeErrors().length === 0;
|
|
1098
1128
|
}
|
|
1099
1129
|
/**
|
|
1100
1130
|
* Reset the component to its initial state.
|
|
@@ -1103,11 +1133,12 @@ class ClientForm {
|
|
|
1103
1133
|
this.loadSub?.unsubscribe();
|
|
1104
1134
|
this.submitSub?.unsubscribe();
|
|
1105
1135
|
this.formControl.reset({});
|
|
1106
|
-
this.
|
|
1136
|
+
this.formRuntimeMessages.set([]);
|
|
1137
|
+
this.submitValidationMessages.set([]);
|
|
1107
1138
|
this.state.reset();
|
|
1108
1139
|
}
|
|
1109
1140
|
onRuntimeMessagesChange(messages) {
|
|
1110
|
-
this.
|
|
1141
|
+
this.formRuntimeMessages.set(messages ?? []);
|
|
1111
1142
|
}
|
|
1112
1143
|
onStepChange(value) {
|
|
1113
1144
|
const count = this.stepSections().length;
|
|
@@ -1155,6 +1186,7 @@ class ClientForm {
|
|
|
1155
1186
|
ngOnDestroy() {
|
|
1156
1187
|
this.loadSub?.unsubscribe();
|
|
1157
1188
|
this.submitSub?.unsubscribe();
|
|
1189
|
+
this.formValueSub?.unsubscribe();
|
|
1158
1190
|
}
|
|
1159
1191
|
// ============================================================================
|
|
1160
1192
|
// Private Helpers
|
|
@@ -1229,6 +1261,45 @@ class ClientForm {
|
|
|
1229
1261
|
loadResponse,
|
|
1230
1262
|
});
|
|
1231
1263
|
}
|
|
1264
|
+
mapSubmitValidationMessages(results) {
|
|
1265
|
+
return results
|
|
1266
|
+
.filter((result) => !result.passed)
|
|
1267
|
+
.map((result) => ({
|
|
1268
|
+
code: result.isEvaluationError
|
|
1269
|
+
? 'FORMULA_EVALUATION_ERROR'
|
|
1270
|
+
: 'FORMULA_FALSE',
|
|
1271
|
+
severity: result.severity,
|
|
1272
|
+
message: this.resolveSubmitValidationMessage(result),
|
|
1273
|
+
ruleId: result.ruleId,
|
|
1274
|
+
}));
|
|
1275
|
+
}
|
|
1276
|
+
resolveSubmitValidationMessage(result) {
|
|
1277
|
+
const translatedMessage = this.resolveTranslatableValue(result.message);
|
|
1278
|
+
if (translatedMessage) {
|
|
1279
|
+
return translatedMessage;
|
|
1280
|
+
}
|
|
1281
|
+
const errorMessage = result.errorMessage?.trim();
|
|
1282
|
+
if (errorMessage) {
|
|
1283
|
+
return errorMessage;
|
|
1284
|
+
}
|
|
1285
|
+
return 'Form validation failed';
|
|
1286
|
+
}
|
|
1287
|
+
resolveTranslatableValue(value) {
|
|
1288
|
+
if (typeof value === 'string') {
|
|
1289
|
+
const normalized = value.trim();
|
|
1290
|
+
return normalized.length > 0 ? normalized : null;
|
|
1291
|
+
}
|
|
1292
|
+
if (!value) {
|
|
1293
|
+
return null;
|
|
1294
|
+
}
|
|
1295
|
+
const lang = this.lang();
|
|
1296
|
+
const localized = value.display ?? value[lang] ?? value.en ?? value.ar ?? null;
|
|
1297
|
+
if (typeof localized !== 'string') {
|
|
1298
|
+
return null;
|
|
1299
|
+
}
|
|
1300
|
+
const normalized = localized.trim();
|
|
1301
|
+
return normalized.length > 0 ? normalized : null;
|
|
1302
|
+
}
|
|
1232
1303
|
resolveStepTimelineState(stepValue, currentStep) {
|
|
1233
1304
|
if (stepValue < currentStep) {
|
|
1234
1305
|
return 'completed';
|