@masterteam/forms 0.0.59 → 0.0.60
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 +93 -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
|
/**
|
|
@@ -454,19 +460,6 @@ function buildFormulaCondition(field) {
|
|
|
454
460
|
mode: 'auto',
|
|
455
461
|
};
|
|
456
462
|
}
|
|
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
463
|
/**
|
|
471
464
|
* Resolve lookup items for Lookup / LookupMultiSelect viewTypes.
|
|
472
465
|
*
|
|
@@ -717,8 +710,14 @@ class ClientForm {
|
|
|
717
710
|
state = inject(ClientFormStateService);
|
|
718
711
|
loadSub;
|
|
719
712
|
submitSub;
|
|
713
|
+
formValueSub;
|
|
720
714
|
hasStartedLoad = signal(false, ...(ngDevMode ? [{ debugName: "hasStartedLoad" }] : []));
|
|
721
|
-
|
|
715
|
+
formRuntimeMessages = signal([], ...(ngDevMode ? [{ debugName: "formRuntimeMessages" }] : []));
|
|
716
|
+
submitValidationMessages = signal([], ...(ngDevMode ? [{ debugName: "submitValidationMessages" }] : []));
|
|
717
|
+
runtimeMessages = computed(() => [
|
|
718
|
+
...this.formRuntimeMessages(),
|
|
719
|
+
...this.submitValidationMessages(),
|
|
720
|
+
], ...(ngDevMode ? [{ debugName: "runtimeMessages" }] : []));
|
|
722
721
|
translocoService = inject(TranslocoService);
|
|
723
722
|
// ============================================================================
|
|
724
723
|
// Public State Signals (for parent access via viewChild)
|
|
@@ -950,6 +949,11 @@ class ClientForm {
|
|
|
950
949
|
// Effects
|
|
951
950
|
// ============================================================================
|
|
952
951
|
constructor() {
|
|
952
|
+
this.formValueSub = this.formControl.valueChanges.subscribe(() => {
|
|
953
|
+
if (this.submitValidationMessages().length > 0) {
|
|
954
|
+
this.submitValidationMessages.set([]);
|
|
955
|
+
}
|
|
956
|
+
});
|
|
953
957
|
// Auto-load when inputs are ready
|
|
954
958
|
effect(() => {
|
|
955
959
|
const autoLoad = this.autoLoad();
|
|
@@ -1000,7 +1004,8 @@ class ClientForm {
|
|
|
1000
1004
|
return;
|
|
1001
1005
|
this.loadSub?.unsubscribe();
|
|
1002
1006
|
this.hasStartedLoad.set(true);
|
|
1003
|
-
this.
|
|
1007
|
+
this.formRuntimeMessages.set([]);
|
|
1008
|
+
this.submitValidationMessages.set([]);
|
|
1004
1009
|
this.state.loading.set(true);
|
|
1005
1010
|
this.state.error.set(null);
|
|
1006
1011
|
this.state.submitResponse.set(null);
|
|
@@ -1049,8 +1054,30 @@ class ClientForm {
|
|
|
1049
1054
|
this.state.submitting.set(true);
|
|
1050
1055
|
this.state.submitError.set(null);
|
|
1051
1056
|
const request = this.buildSubmitRequest(loadResponse);
|
|
1052
|
-
this.
|
|
1053
|
-
|
|
1057
|
+
this.submitValidationMessages.set([]);
|
|
1058
|
+
this.submitSub = this.api
|
|
1059
|
+
.validate(request)
|
|
1060
|
+
.pipe(switchMap((validationResponse) => {
|
|
1061
|
+
const validation = validationResponse.data;
|
|
1062
|
+
if (!validation) {
|
|
1063
|
+
const msg = validationResponse.message ?? 'Failed to validate form';
|
|
1064
|
+
this.state.submitting.set(false);
|
|
1065
|
+
this.state.setSubmitError(msg);
|
|
1066
|
+
this.errored.emit(msg);
|
|
1067
|
+
return EMPTY;
|
|
1068
|
+
}
|
|
1069
|
+
this.submitValidationMessages.set(this.mapSubmitValidationMessages(validation.results ?? []));
|
|
1070
|
+
if (validation.hasBlockingFailures) {
|
|
1071
|
+
this.state.submitting.set(false);
|
|
1072
|
+
return EMPTY;
|
|
1073
|
+
}
|
|
1074
|
+
return this.api.submit(request).pipe(map((response) => ({
|
|
1075
|
+
kind: 'submit',
|
|
1076
|
+
response,
|
|
1077
|
+
})));
|
|
1078
|
+
}))
|
|
1079
|
+
.subscribe({
|
|
1080
|
+
next: ({ response }) => {
|
|
1054
1081
|
this.state.submitting.set(false);
|
|
1055
1082
|
if (response.data) {
|
|
1056
1083
|
this.state.setSubmitResponse(response.data);
|
|
@@ -1063,7 +1090,9 @@ class ClientForm {
|
|
|
1063
1090
|
}
|
|
1064
1091
|
},
|
|
1065
1092
|
error: (err) => {
|
|
1066
|
-
const msg = err?.error?.message ??
|
|
1093
|
+
const msg = err?.error?.message ??
|
|
1094
|
+
err?.message ??
|
|
1095
|
+
'Failed to validate or submit form';
|
|
1067
1096
|
this.state.setSubmitError(msg);
|
|
1068
1097
|
this.errored.emit(msg);
|
|
1069
1098
|
},
|
|
@@ -1094,7 +1123,7 @@ class ClientForm {
|
|
|
1094
1123
|
* Check whether the current form state is valid.
|
|
1095
1124
|
*/
|
|
1096
1125
|
isValid() {
|
|
1097
|
-
return this.formControl.valid;
|
|
1126
|
+
return this.formControl.valid && this.runtimeErrors().length === 0;
|
|
1098
1127
|
}
|
|
1099
1128
|
/**
|
|
1100
1129
|
* Reset the component to its initial state.
|
|
@@ -1103,11 +1132,12 @@ class ClientForm {
|
|
|
1103
1132
|
this.loadSub?.unsubscribe();
|
|
1104
1133
|
this.submitSub?.unsubscribe();
|
|
1105
1134
|
this.formControl.reset({});
|
|
1106
|
-
this.
|
|
1135
|
+
this.formRuntimeMessages.set([]);
|
|
1136
|
+
this.submitValidationMessages.set([]);
|
|
1107
1137
|
this.state.reset();
|
|
1108
1138
|
}
|
|
1109
1139
|
onRuntimeMessagesChange(messages) {
|
|
1110
|
-
this.
|
|
1140
|
+
this.formRuntimeMessages.set(messages ?? []);
|
|
1111
1141
|
}
|
|
1112
1142
|
onStepChange(value) {
|
|
1113
1143
|
const count = this.stepSections().length;
|
|
@@ -1155,6 +1185,7 @@ class ClientForm {
|
|
|
1155
1185
|
ngOnDestroy() {
|
|
1156
1186
|
this.loadSub?.unsubscribe();
|
|
1157
1187
|
this.submitSub?.unsubscribe();
|
|
1188
|
+
this.formValueSub?.unsubscribe();
|
|
1158
1189
|
}
|
|
1159
1190
|
// ============================================================================
|
|
1160
1191
|
// Private Helpers
|
|
@@ -1229,6 +1260,45 @@ class ClientForm {
|
|
|
1229
1260
|
loadResponse,
|
|
1230
1261
|
});
|
|
1231
1262
|
}
|
|
1263
|
+
mapSubmitValidationMessages(results) {
|
|
1264
|
+
return results
|
|
1265
|
+
.filter((result) => !result.passed)
|
|
1266
|
+
.map((result) => ({
|
|
1267
|
+
code: result.isEvaluationError
|
|
1268
|
+
? 'FORMULA_EVALUATION_ERROR'
|
|
1269
|
+
: 'FORMULA_FALSE',
|
|
1270
|
+
severity: result.severity,
|
|
1271
|
+
message: this.resolveSubmitValidationMessage(result),
|
|
1272
|
+
ruleId: result.ruleId,
|
|
1273
|
+
}));
|
|
1274
|
+
}
|
|
1275
|
+
resolveSubmitValidationMessage(result) {
|
|
1276
|
+
const translatedMessage = this.resolveTranslatableValue(result.message);
|
|
1277
|
+
if (translatedMessage) {
|
|
1278
|
+
return translatedMessage;
|
|
1279
|
+
}
|
|
1280
|
+
const errorMessage = result.errorMessage?.trim();
|
|
1281
|
+
if (errorMessage) {
|
|
1282
|
+
return errorMessage;
|
|
1283
|
+
}
|
|
1284
|
+
return 'Form validation failed';
|
|
1285
|
+
}
|
|
1286
|
+
resolveTranslatableValue(value) {
|
|
1287
|
+
if (typeof value === 'string') {
|
|
1288
|
+
const normalized = value.trim();
|
|
1289
|
+
return normalized.length > 0 ? normalized : null;
|
|
1290
|
+
}
|
|
1291
|
+
if (!value) {
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
const lang = this.lang();
|
|
1295
|
+
const localized = value.display ?? value[lang] ?? value.en ?? value.ar ?? null;
|
|
1296
|
+
if (typeof localized !== 'string') {
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
const normalized = localized.trim();
|
|
1300
|
+
return normalized.length > 0 ? normalized : null;
|
|
1301
|
+
}
|
|
1232
1302
|
resolveStepTimelineState(stepValue, currentStep) {
|
|
1233
1303
|
if (stepValue < currentStep) {
|
|
1234
1304
|
return 'completed';
|