@masterteam/form-builder 0.0.2 → 0.0.4
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/form-builder.css +2 -4
- package/fesm2022/masterteam-form-builder.mjs +1908 -0
- package/fesm2022/masterteam-form-builder.mjs.map +1 -0
- package/package.json +16 -16
- package/types/masterteam-form-builder.d.ts +299 -0
- package/.angular/cache/21.0.2/ng-packagr/db70d8f07b5a2d2d1c3124ca92e8d56d14fb894dce4d4867ba7c0db29ba913a3 +0 -1
- package/.angular/cache/21.0.2/ng-packagr/tsbuildinfo/masterteam-form-builder.tsbuildinfo +0 -1
- package/BACKEND_API_SPEC.md +0 -338
- package/angular.json +0 -26
- package/ng-package.json +0 -13
- package/src/lib/fb-field-conditions/condition-constants.ts +0 -262
- package/src/lib/fb-field-conditions/fb-field-conditions.html +0 -35
- package/src/lib/fb-field-conditions/fb-field-conditions.ts +0 -123
- package/src/lib/fb-field-form/fb-field-form.html +0 -59
- package/src/lib/fb-field-form/fb-field-form.ts +0 -250
- package/src/lib/fb-preview-form/fb-preview-form.html +0 -31
- package/src/lib/fb-preview-form/fb-preview-form.ts +0 -147
- package/src/lib/fb-section/fb-section.html +0 -130
- package/src/lib/fb-section/fb-section.ts +0 -211
- package/src/lib/fb-section-form/fb-section-form.html +0 -38
- package/src/lib/fb-section-form/fb-section-form.ts +0 -128
- package/src/lib/form-builder.html +0 -166
- package/src/lib/form-builder.model.ts +0 -27
- package/src/lib/form-builder.scss +0 -20
- package/src/lib/form-builder.ts +0 -208
- package/src/public-api.ts +0 -6
- package/src/store/form-builder/api.model.ts +0 -13
- package/src/store/form-builder/form-builder.actions.ts +0 -113
- package/src/store/form-builder/form-builder.facade.ts +0 -207
- package/src/store/form-builder/form-builder.model.ts +0 -147
- package/src/store/form-builder/form-builder.state.ts +0 -668
- package/src/store/form-builder/index.ts +0 -5
- package/tsconfig.json +0 -31
|
@@ -0,0 +1,1908 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, Injectable, computed, input, signal, viewChild, Component, effect, output } from '@angular/core';
|
|
3
|
+
import * as i4 from '@angular/common';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
import * as i1 from '@angular/forms';
|
|
6
|
+
import { FormControl, ReactiveFormsModule, FormGroup, FormsModule } from '@angular/forms';
|
|
7
|
+
import * as i1$1 from 'primeng/tabs';
|
|
8
|
+
import { TabsModule } from 'primeng/tabs';
|
|
9
|
+
import * as i2 from 'primeng/skeleton';
|
|
10
|
+
import { SkeletonModule } from 'primeng/skeleton';
|
|
11
|
+
import { Button } from '@masterteam/components/button';
|
|
12
|
+
import { Card } from '@masterteam/components/card';
|
|
13
|
+
import { ModalService } from '@masterteam/components/modal';
|
|
14
|
+
import { ConfirmationService } from '@masterteam/components/confirmation';
|
|
15
|
+
import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
|
|
16
|
+
import * as i3 from '@angular/cdk/drag-drop';
|
|
17
|
+
import { CdkDrag, CdkDropList, CdkDragPlaceholder, DragDropModule } from '@angular/cdk/drag-drop';
|
|
18
|
+
import { DynamicField } from '@masterteam/forms/dynamic-field';
|
|
19
|
+
import { Icon } from '@masterteam/icons';
|
|
20
|
+
import { Action, Selector, State, Store, select } from '@ngxs/store';
|
|
21
|
+
import { HttpClient } from '@angular/common/http';
|
|
22
|
+
import { CrudStateBase, handleApiRequest, RadioCardsFieldConfig } from '@masterteam/components';
|
|
23
|
+
import { DynamicForm } from '@masterteam/forms/dynamic-form';
|
|
24
|
+
import { ToggleField } from '@masterteam/components/toggle-field';
|
|
25
|
+
import { ModalRef } from '@masterteam/components/dialog';
|
|
26
|
+
import { FormulaToolbar, FormulaEditor } from '@masterteam/components/formula';
|
|
27
|
+
import { Tabs } from '@masterteam/components/tabs';
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Module Configuration Actions
|
|
31
|
+
// ============================================================================
|
|
32
|
+
class SetModuleInfo {
|
|
33
|
+
moduleType;
|
|
34
|
+
moduleId;
|
|
35
|
+
parentModuleType;
|
|
36
|
+
parentModuleId;
|
|
37
|
+
parentPath;
|
|
38
|
+
static type = '[FormBuilder] Set Module Info';
|
|
39
|
+
constructor(moduleType, moduleId, parentModuleType, parentModuleId, parentPath) {
|
|
40
|
+
this.moduleType = moduleType;
|
|
41
|
+
this.moduleId = moduleId;
|
|
42
|
+
this.parentModuleType = parentModuleType;
|
|
43
|
+
this.parentModuleId = parentModuleId;
|
|
44
|
+
this.parentPath = parentPath;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
class ResetFormBuilderState {
|
|
48
|
+
static type = '[FormBuilder] Reset State';
|
|
49
|
+
}
|
|
50
|
+
class SetProperties {
|
|
51
|
+
properties;
|
|
52
|
+
static type = '[FormBuilder] Set Properties';
|
|
53
|
+
constructor(properties) {
|
|
54
|
+
this.properties = properties;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Form Configuration Actions
|
|
59
|
+
// ============================================================================
|
|
60
|
+
class GetFormConfiguration {
|
|
61
|
+
static type = '[FormBuilder] Get Form Configuration';
|
|
62
|
+
}
|
|
63
|
+
class ResetFormConfiguration {
|
|
64
|
+
static type = '[FormBuilder] Reset Form Configuration';
|
|
65
|
+
}
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Section Actions
|
|
68
|
+
// ============================================================================
|
|
69
|
+
class AddSection {
|
|
70
|
+
payload;
|
|
71
|
+
static type = '[FormBuilder] Add Section';
|
|
72
|
+
constructor(payload) {
|
|
73
|
+
this.payload = payload;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
class UpdateSection {
|
|
77
|
+
sectionId;
|
|
78
|
+
payload;
|
|
79
|
+
static type = '[FormBuilder] Update Section';
|
|
80
|
+
constructor(sectionId, payload) {
|
|
81
|
+
this.sectionId = sectionId;
|
|
82
|
+
this.payload = payload;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
class DeleteSection {
|
|
86
|
+
sectionId;
|
|
87
|
+
static type = '[FormBuilder] Delete Section';
|
|
88
|
+
constructor(sectionId) {
|
|
89
|
+
this.sectionId = sectionId;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Field Actions
|
|
94
|
+
// ============================================================================
|
|
95
|
+
class AddField {
|
|
96
|
+
sectionId;
|
|
97
|
+
payload;
|
|
98
|
+
static type = '[FormBuilder] Add Field';
|
|
99
|
+
constructor(sectionId, payload) {
|
|
100
|
+
this.sectionId = sectionId;
|
|
101
|
+
this.payload = payload;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
class UpdateField {
|
|
105
|
+
sectionId;
|
|
106
|
+
fieldId;
|
|
107
|
+
payload;
|
|
108
|
+
static type = '[FormBuilder] Update Field';
|
|
109
|
+
constructor(sectionId, fieldId, payload) {
|
|
110
|
+
this.sectionId = sectionId;
|
|
111
|
+
this.fieldId = fieldId;
|
|
112
|
+
this.payload = payload;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
class DeleteField {
|
|
116
|
+
sectionId;
|
|
117
|
+
fieldId;
|
|
118
|
+
static type = '[FormBuilder] Delete Field';
|
|
119
|
+
constructor(sectionId, fieldId) {
|
|
120
|
+
this.sectionId = sectionId;
|
|
121
|
+
this.fieldId = fieldId;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
class ReorderFields {
|
|
125
|
+
sectionId;
|
|
126
|
+
payload;
|
|
127
|
+
static type = '[FormBuilder] Reorder Fields';
|
|
128
|
+
constructor(sectionId, payload) {
|
|
129
|
+
this.sectionId = sectionId;
|
|
130
|
+
this.payload = payload;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
class MoveField {
|
|
134
|
+
sectionId;
|
|
135
|
+
fieldId;
|
|
136
|
+
payload;
|
|
137
|
+
static type = '[FormBuilder] Move Field';
|
|
138
|
+
constructor(sectionId, fieldId, payload) {
|
|
139
|
+
this.sectionId = sectionId;
|
|
140
|
+
this.fieldId = fieldId;
|
|
141
|
+
this.payload = payload;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Action Keys Enum
|
|
147
|
+
// ============================================================================
|
|
148
|
+
var FormBuilderActionKey;
|
|
149
|
+
(function (FormBuilderActionKey) {
|
|
150
|
+
// Form Configuration
|
|
151
|
+
FormBuilderActionKey["GetFormConfiguration"] = "getFormConfiguration";
|
|
152
|
+
FormBuilderActionKey["ResetFormConfiguration"] = "resetFormConfiguration";
|
|
153
|
+
// Section CRUD
|
|
154
|
+
FormBuilderActionKey["AddSection"] = "addSection";
|
|
155
|
+
FormBuilderActionKey["UpdateSection"] = "updateSection";
|
|
156
|
+
FormBuilderActionKey["DeleteSection"] = "deleteSection";
|
|
157
|
+
// Field CRUD
|
|
158
|
+
FormBuilderActionKey["AddField"] = "addField";
|
|
159
|
+
FormBuilderActionKey["UpdateField"] = "updateField";
|
|
160
|
+
FormBuilderActionKey["DeleteField"] = "deleteField";
|
|
161
|
+
FormBuilderActionKey["MoveField"] = "moveField";
|
|
162
|
+
FormBuilderActionKey["ReorderFields"] = "reorderFields";
|
|
163
|
+
})(FormBuilderActionKey || (FormBuilderActionKey = {}));
|
|
164
|
+
|
|
165
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
166
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
167
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
168
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
169
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
170
|
+
};
|
|
171
|
+
// Default State
|
|
172
|
+
const DEFAULT_STATE = {
|
|
173
|
+
// Module configuration
|
|
174
|
+
moduleType: null,
|
|
175
|
+
moduleId: null,
|
|
176
|
+
parentModuleType: null,
|
|
177
|
+
parentModuleId: null,
|
|
178
|
+
parentPath: '',
|
|
179
|
+
// Form data
|
|
180
|
+
formConfiguration: null,
|
|
181
|
+
// Properties for field enrichment
|
|
182
|
+
properties: [],
|
|
183
|
+
// Loading state (from LoadingStateShape)
|
|
184
|
+
loadingActive: [],
|
|
185
|
+
errors: {},
|
|
186
|
+
};
|
|
187
|
+
let FormBuilderState = class FormBuilderState extends CrudStateBase {
|
|
188
|
+
http = inject(HttpClient);
|
|
189
|
+
baseUrl = 'formConfigurations';
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// Helpers
|
|
192
|
+
// ============================================================================
|
|
193
|
+
getApiPath(state) {
|
|
194
|
+
const { parentPath, moduleType, moduleId } = state;
|
|
195
|
+
return `${this.baseUrl}${parentPath}/${moduleType}/${moduleId}`;
|
|
196
|
+
}
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// Selectors
|
|
199
|
+
// ============================================================================
|
|
200
|
+
static getState(state) {
|
|
201
|
+
return state ?? DEFAULT_STATE;
|
|
202
|
+
}
|
|
203
|
+
static getFormConfiguration(state) {
|
|
204
|
+
return state?.formConfiguration ?? null;
|
|
205
|
+
}
|
|
206
|
+
static getSections(state) {
|
|
207
|
+
return state?.formConfiguration?.sections ?? [];
|
|
208
|
+
}
|
|
209
|
+
static getModuleType(state) {
|
|
210
|
+
return state?.moduleType ?? null;
|
|
211
|
+
}
|
|
212
|
+
static getModuleId(state) {
|
|
213
|
+
return state?.moduleId ?? null;
|
|
214
|
+
}
|
|
215
|
+
static getProperties(state) {
|
|
216
|
+
return state?.properties ?? [];
|
|
217
|
+
}
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// Module Configuration Actions
|
|
220
|
+
// ============================================================================
|
|
221
|
+
setModuleInfo(ctx, action) {
|
|
222
|
+
let parentPath = '';
|
|
223
|
+
if (action.parentModuleType && action.parentModuleId) {
|
|
224
|
+
parentPath = `/${action.parentModuleType}/${action.parentModuleId}`;
|
|
225
|
+
}
|
|
226
|
+
else if (action.parentPath) {
|
|
227
|
+
parentPath = action.parentPath;
|
|
228
|
+
}
|
|
229
|
+
ctx.patchState({
|
|
230
|
+
moduleType: action.moduleType,
|
|
231
|
+
moduleId: action.moduleId,
|
|
232
|
+
parentModuleType: action.parentModuleType ?? null,
|
|
233
|
+
parentModuleId: action.parentModuleId ?? null,
|
|
234
|
+
parentPath: parentPath ?? '',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
resetState(ctx) {
|
|
238
|
+
ctx.setState(DEFAULT_STATE);
|
|
239
|
+
}
|
|
240
|
+
setProperties(ctx, action) {
|
|
241
|
+
ctx.patchState({
|
|
242
|
+
properties: action.properties,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// Form Configuration Actions
|
|
247
|
+
// ============================================================================
|
|
248
|
+
getFormConfiguration(ctx) {
|
|
249
|
+
const state = ctx.getState();
|
|
250
|
+
const apiPath = this.getApiPath(state);
|
|
251
|
+
const req$ = this.http.get(apiPath);
|
|
252
|
+
return this.load(ctx, {
|
|
253
|
+
key: FormBuilderActionKey.GetFormConfiguration,
|
|
254
|
+
request$: req$,
|
|
255
|
+
updateState: (_state, data) => ({
|
|
256
|
+
formConfiguration: data ?? null,
|
|
257
|
+
}),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
resetFormConfiguration(ctx) {
|
|
261
|
+
const state = ctx.getState();
|
|
262
|
+
const apiPath = `${this.getApiPath(state)}/reset`;
|
|
263
|
+
const req$ = this.http.delete(apiPath);
|
|
264
|
+
return handleApiRequest({
|
|
265
|
+
ctx,
|
|
266
|
+
key: FormBuilderActionKey.ResetFormConfiguration,
|
|
267
|
+
request$: req$,
|
|
268
|
+
onSuccess: (res, _currentState) => ({
|
|
269
|
+
formConfiguration: res.data ?? null,
|
|
270
|
+
}),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
// ============================================================================
|
|
274
|
+
// Section Actions
|
|
275
|
+
// ============================================================================
|
|
276
|
+
addSection(ctx, action) {
|
|
277
|
+
const state = ctx.getState();
|
|
278
|
+
const apiPath = `${this.getApiPath(state)}/sections`;
|
|
279
|
+
const req$ = this.http.post(apiPath, action.payload);
|
|
280
|
+
return handleApiRequest({
|
|
281
|
+
ctx,
|
|
282
|
+
key: FormBuilderActionKey.AddSection,
|
|
283
|
+
request$: req$,
|
|
284
|
+
onSuccess: (res, currentState) => {
|
|
285
|
+
const sections = this.adapter.addOne(currentState.formConfiguration?.sections ?? [], res.data);
|
|
286
|
+
return {
|
|
287
|
+
formConfiguration: {
|
|
288
|
+
...currentState.formConfiguration,
|
|
289
|
+
sections,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
updateSection(ctx, action) {
|
|
296
|
+
const state = ctx.getState();
|
|
297
|
+
const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}`;
|
|
298
|
+
const req$ = this.http.put(apiPath, action.payload);
|
|
299
|
+
return handleApiRequest({
|
|
300
|
+
ctx,
|
|
301
|
+
key: FormBuilderActionKey.UpdateSection,
|
|
302
|
+
request$: req$,
|
|
303
|
+
onSuccess: (res, currentState) => {
|
|
304
|
+
const sections = this.adapter.upsertOne(currentState.formConfiguration?.sections ?? [], res.data, 'id');
|
|
305
|
+
return {
|
|
306
|
+
formConfiguration: {
|
|
307
|
+
...currentState.formConfiguration,
|
|
308
|
+
sections,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
deleteSection(ctx, action) {
|
|
315
|
+
const state = ctx.getState();
|
|
316
|
+
const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}`;
|
|
317
|
+
const req$ = this.http.delete(apiPath);
|
|
318
|
+
return handleApiRequest({
|
|
319
|
+
ctx,
|
|
320
|
+
key: FormBuilderActionKey.DeleteSection,
|
|
321
|
+
request$: req$,
|
|
322
|
+
onSuccess: (res, currentState) => {
|
|
323
|
+
const sections = this.adapter.removeOne(currentState.formConfiguration?.sections ?? [], res.data.id, 'id');
|
|
324
|
+
return {
|
|
325
|
+
formConfiguration: {
|
|
326
|
+
...currentState.formConfiguration,
|
|
327
|
+
sections,
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// Field Actions
|
|
335
|
+
// ============================================================================
|
|
336
|
+
addField(ctx, action) {
|
|
337
|
+
const state = ctx.getState();
|
|
338
|
+
const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields`;
|
|
339
|
+
const req$ = this.http.post(apiPath, action.payload);
|
|
340
|
+
// Generate temp ID for optimistic update
|
|
341
|
+
const tempId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
342
|
+
const tempField = {
|
|
343
|
+
id: tempId,
|
|
344
|
+
sectionId: action.sectionId,
|
|
345
|
+
propertyId: action.payload.propertyId,
|
|
346
|
+
width: action.payload.width,
|
|
347
|
+
order: action.payload.order ?? 0,
|
|
348
|
+
hiddenInCreation: action.payload.hiddenInCreation,
|
|
349
|
+
_pending: true,
|
|
350
|
+
};
|
|
351
|
+
// Optimistically add the temp field
|
|
352
|
+
const sectionsWithTemp = (state.formConfiguration?.sections ?? []).map((section) => {
|
|
353
|
+
if (section.id === action.sectionId) {
|
|
354
|
+
const fields = [...section.fields];
|
|
355
|
+
const insertIndex = action.payload.order ?? fields.length;
|
|
356
|
+
fields.splice(insertIndex, 0, tempField);
|
|
357
|
+
return { ...section, fields };
|
|
358
|
+
}
|
|
359
|
+
return section;
|
|
360
|
+
});
|
|
361
|
+
ctx.patchState({
|
|
362
|
+
formConfiguration: {
|
|
363
|
+
...state.formConfiguration,
|
|
364
|
+
sections: sectionsWithTemp,
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
return handleApiRequest({
|
|
368
|
+
ctx,
|
|
369
|
+
key: FormBuilderActionKey.AddField,
|
|
370
|
+
request$: req$,
|
|
371
|
+
onSuccess: (res, currentState) => {
|
|
372
|
+
const newField = res.data;
|
|
373
|
+
// Replace temp field with real field
|
|
374
|
+
const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
|
|
375
|
+
if (section.id === action.sectionId) {
|
|
376
|
+
return {
|
|
377
|
+
...section,
|
|
378
|
+
fields: section.fields.map((f) => f.id === tempId ? newField : f),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return section;
|
|
382
|
+
});
|
|
383
|
+
return {
|
|
384
|
+
formConfiguration: {
|
|
385
|
+
...currentState.formConfiguration,
|
|
386
|
+
sections,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
},
|
|
390
|
+
onError: (_error, currentState) => {
|
|
391
|
+
// Remove temp field on error
|
|
392
|
+
const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
|
|
393
|
+
if (section.id === action.sectionId) {
|
|
394
|
+
return {
|
|
395
|
+
...section,
|
|
396
|
+
fields: section.fields.filter((f) => f.id !== tempId),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return section;
|
|
400
|
+
});
|
|
401
|
+
return {
|
|
402
|
+
formConfiguration: {
|
|
403
|
+
...currentState.formConfiguration,
|
|
404
|
+
sections,
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
updateField(ctx, action) {
|
|
411
|
+
const state = ctx.getState();
|
|
412
|
+
const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields/${action.fieldId}`;
|
|
413
|
+
const req$ = this.http.put(apiPath, action.payload);
|
|
414
|
+
// Apply optimistic update for order changes
|
|
415
|
+
if (action.payload.order !== undefined) {
|
|
416
|
+
const newOrder = action.payload.order;
|
|
417
|
+
const sections = (state.formConfiguration?.sections ?? []).map((section) => {
|
|
418
|
+
if (section.id === action.sectionId) {
|
|
419
|
+
const fields = [...section.fields];
|
|
420
|
+
const currentIndex = fields.findIndex((f) => f.id === action.fieldId);
|
|
421
|
+
if (currentIndex !== -1) {
|
|
422
|
+
const [movedField] = fields.splice(currentIndex, 1);
|
|
423
|
+
fields.splice(newOrder, 0, movedField);
|
|
424
|
+
}
|
|
425
|
+
return { ...section, fields };
|
|
426
|
+
}
|
|
427
|
+
return section;
|
|
428
|
+
});
|
|
429
|
+
ctx.patchState({
|
|
430
|
+
formConfiguration: {
|
|
431
|
+
...state.formConfiguration,
|
|
432
|
+
sections,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
return handleApiRequest({
|
|
437
|
+
ctx,
|
|
438
|
+
key: FormBuilderActionKey.UpdateField,
|
|
439
|
+
request$: req$,
|
|
440
|
+
onSuccess: (res, currentState) => {
|
|
441
|
+
const updatedField = res.data;
|
|
442
|
+
const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
|
|
443
|
+
if (section.id === action.sectionId) {
|
|
444
|
+
// Update the field but preserve current array order (from optimistic update)
|
|
445
|
+
// Don't re-sort by order property as other fields may have stale order values
|
|
446
|
+
const updatedFields = section.fields.map((f, index) => f.id === updatedField.id
|
|
447
|
+
? { ...updatedField, order: index }
|
|
448
|
+
: { ...f, order: index });
|
|
449
|
+
return {
|
|
450
|
+
...section,
|
|
451
|
+
fields: updatedFields,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return section;
|
|
455
|
+
});
|
|
456
|
+
return {
|
|
457
|
+
formConfiguration: {
|
|
458
|
+
...currentState.formConfiguration,
|
|
459
|
+
sections,
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
},
|
|
463
|
+
onError: (_error, _currentState) => {
|
|
464
|
+
// Revert optimistic update on error - reload original state
|
|
465
|
+
if (action.payload.order !== undefined) {
|
|
466
|
+
return {
|
|
467
|
+
formConfiguration: state.formConfiguration,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return {};
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
deleteField(ctx, action) {
|
|
475
|
+
const state = ctx.getState();
|
|
476
|
+
const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields/${action.fieldId}`;
|
|
477
|
+
const req$ = this.http.delete(apiPath);
|
|
478
|
+
// Optimistically mark field as deleting
|
|
479
|
+
const sectionsWithDeleting = (state.formConfiguration?.sections ?? []).map((section) => {
|
|
480
|
+
if (section.id === action.sectionId) {
|
|
481
|
+
return {
|
|
482
|
+
...section,
|
|
483
|
+
fields: section.fields.map((f) => f.id === action.fieldId ? { ...f, _deleting: true } : f),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return section;
|
|
487
|
+
});
|
|
488
|
+
ctx.patchState({
|
|
489
|
+
formConfiguration: {
|
|
490
|
+
...state.formConfiguration,
|
|
491
|
+
sections: sectionsWithDeleting,
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
return handleApiRequest({
|
|
495
|
+
ctx,
|
|
496
|
+
key: FormBuilderActionKey.DeleteField,
|
|
497
|
+
request$: req$,
|
|
498
|
+
onSuccess: (res, currentState) => {
|
|
499
|
+
const { id: deletedId, sectionId } = res.data;
|
|
500
|
+
const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
|
|
501
|
+
if (section.id === sectionId) {
|
|
502
|
+
return {
|
|
503
|
+
...section,
|
|
504
|
+
fields: section.fields.filter((f) => f.id !== deletedId),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
return section;
|
|
508
|
+
});
|
|
509
|
+
return {
|
|
510
|
+
formConfiguration: {
|
|
511
|
+
...currentState.formConfiguration,
|
|
512
|
+
sections,
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
},
|
|
516
|
+
onError: (_error, currentState) => {
|
|
517
|
+
// Remove _deleting flag on error
|
|
518
|
+
const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
|
|
519
|
+
if (section.id === action.sectionId) {
|
|
520
|
+
return {
|
|
521
|
+
...section,
|
|
522
|
+
fields: section.fields.map((f) => f.id === action.fieldId ? { ...f, _deleting: false } : f),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return section;
|
|
526
|
+
});
|
|
527
|
+
return {
|
|
528
|
+
formConfiguration: {
|
|
529
|
+
...currentState.formConfiguration,
|
|
530
|
+
sections,
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
reorderFields(ctx, action) {
|
|
537
|
+
const state = ctx.getState();
|
|
538
|
+
const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields/reorder`;
|
|
539
|
+
const req$ = this.http.put(apiPath, action.payload);
|
|
540
|
+
// Create order map for optimistic update
|
|
541
|
+
const orderMap = new Map(action.payload.map((p) => [p.id, p.order]));
|
|
542
|
+
// Apply optimistic update
|
|
543
|
+
const optimisticSections = (state.formConfiguration?.sections ?? []).map((section) => {
|
|
544
|
+
if (section.id === action.sectionId) {
|
|
545
|
+
const reorderedFields = section.fields
|
|
546
|
+
.map((f) => ({
|
|
547
|
+
...f,
|
|
548
|
+
order: orderMap.get(f.id) ?? f.order,
|
|
549
|
+
}))
|
|
550
|
+
.sort((a, b) => a.order - b.order);
|
|
551
|
+
return { ...section, fields: reorderedFields };
|
|
552
|
+
}
|
|
553
|
+
return section;
|
|
554
|
+
});
|
|
555
|
+
ctx.patchState({
|
|
556
|
+
formConfiguration: {
|
|
557
|
+
...state.formConfiguration,
|
|
558
|
+
sections: optimisticSections,
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
return handleApiRequest({
|
|
562
|
+
ctx,
|
|
563
|
+
key: FormBuilderActionKey.ReorderFields,
|
|
564
|
+
request$: req$,
|
|
565
|
+
onSuccess: (res, currentState) => {
|
|
566
|
+
const updatedFields = res.data.fields;
|
|
567
|
+
const fieldMap = new Map(updatedFields.map((f) => [f.id, f]));
|
|
568
|
+
const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
|
|
569
|
+
if (section.id === action.sectionId) {
|
|
570
|
+
const mergedFields = section.fields
|
|
571
|
+
.map((f) => fieldMap.get(f.id) ?? f)
|
|
572
|
+
.sort((a, b) => a.order - b.order);
|
|
573
|
+
return { ...section, fields: mergedFields };
|
|
574
|
+
}
|
|
575
|
+
return section;
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
formConfiguration: {
|
|
579
|
+
...currentState.formConfiguration,
|
|
580
|
+
sections,
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
},
|
|
584
|
+
onError: (_error, _currentState) => {
|
|
585
|
+
// Revert to original state on error
|
|
586
|
+
return {
|
|
587
|
+
formConfiguration: state.formConfiguration,
|
|
588
|
+
};
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
moveField(ctx, action) {
|
|
593
|
+
const state = ctx.getState();
|
|
594
|
+
const apiPath = `${this.getApiPath(state)}/sections/${action.sectionId}/fields/${action.fieldId}/move`;
|
|
595
|
+
const req$ = this.http.put(apiPath, action.payload);
|
|
596
|
+
// Apply optimistic update - move field immediately
|
|
597
|
+
const sourceSectionId = action.sectionId;
|
|
598
|
+
const targetSectionId = action.payload.targetSectionId;
|
|
599
|
+
const targetOrder = action.payload.order ?? 0;
|
|
600
|
+
let movedFieldData = null;
|
|
601
|
+
const optimisticSections = (state.formConfiguration?.sections ?? []).map((section) => {
|
|
602
|
+
if (section.id === sourceSectionId) {
|
|
603
|
+
const fieldToMove = section.fields.find((f) => f.id === action.fieldId);
|
|
604
|
+
if (fieldToMove) {
|
|
605
|
+
movedFieldData = { ...fieldToMove, order: targetOrder };
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
...section,
|
|
609
|
+
fields: section.fields.filter((f) => f.id !== action.fieldId),
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
return section;
|
|
613
|
+
});
|
|
614
|
+
// Add to target section
|
|
615
|
+
const sectionsWithMoved = optimisticSections.map((section) => {
|
|
616
|
+
if (section.id === targetSectionId && movedFieldData) {
|
|
617
|
+
const fields = [...section.fields];
|
|
618
|
+
fields.splice(targetOrder, 0, movedFieldData);
|
|
619
|
+
return { ...section, fields };
|
|
620
|
+
}
|
|
621
|
+
return section;
|
|
622
|
+
});
|
|
623
|
+
ctx.patchState({
|
|
624
|
+
formConfiguration: {
|
|
625
|
+
...state.formConfiguration,
|
|
626
|
+
sections: sectionsWithMoved,
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
return handleApiRequest({
|
|
630
|
+
ctx,
|
|
631
|
+
key: FormBuilderActionKey.MoveField,
|
|
632
|
+
request$: req$,
|
|
633
|
+
onSuccess: (res, currentState) => {
|
|
634
|
+
const movedField = res.data;
|
|
635
|
+
const sections = (currentState.formConfiguration?.sections ?? []).map((section) => {
|
|
636
|
+
// Update the moved field with server response
|
|
637
|
+
if (section.id === targetSectionId) {
|
|
638
|
+
return {
|
|
639
|
+
...section,
|
|
640
|
+
fields: section.fields
|
|
641
|
+
.map((f) => (f.id === movedField.id ? movedField : f))
|
|
642
|
+
.sort((a, b) => a.order - b.order),
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return section;
|
|
646
|
+
});
|
|
647
|
+
return {
|
|
648
|
+
formConfiguration: {
|
|
649
|
+
...currentState.formConfiguration,
|
|
650
|
+
sections,
|
|
651
|
+
},
|
|
652
|
+
};
|
|
653
|
+
},
|
|
654
|
+
onError: (_error, _currentState) => {
|
|
655
|
+
// Revert optimistic update on error
|
|
656
|
+
return {
|
|
657
|
+
formConfiguration: state.formConfiguration,
|
|
658
|
+
};
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
663
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderState });
|
|
664
|
+
};
|
|
665
|
+
__decorate([
|
|
666
|
+
Action(SetModuleInfo)
|
|
667
|
+
], FormBuilderState.prototype, "setModuleInfo", null);
|
|
668
|
+
__decorate([
|
|
669
|
+
Action(ResetFormBuilderState)
|
|
670
|
+
], FormBuilderState.prototype, "resetState", null);
|
|
671
|
+
__decorate([
|
|
672
|
+
Action(SetProperties)
|
|
673
|
+
], FormBuilderState.prototype, "setProperties", null);
|
|
674
|
+
__decorate([
|
|
675
|
+
Action(GetFormConfiguration)
|
|
676
|
+
], FormBuilderState.prototype, "getFormConfiguration", null);
|
|
677
|
+
__decorate([
|
|
678
|
+
Action(ResetFormConfiguration)
|
|
679
|
+
], FormBuilderState.prototype, "resetFormConfiguration", null);
|
|
680
|
+
__decorate([
|
|
681
|
+
Action(AddSection)
|
|
682
|
+
], FormBuilderState.prototype, "addSection", null);
|
|
683
|
+
__decorate([
|
|
684
|
+
Action(UpdateSection)
|
|
685
|
+
], FormBuilderState.prototype, "updateSection", null);
|
|
686
|
+
__decorate([
|
|
687
|
+
Action(DeleteSection)
|
|
688
|
+
], FormBuilderState.prototype, "deleteSection", null);
|
|
689
|
+
__decorate([
|
|
690
|
+
Action(AddField)
|
|
691
|
+
], FormBuilderState.prototype, "addField", null);
|
|
692
|
+
__decorate([
|
|
693
|
+
Action(UpdateField)
|
|
694
|
+
], FormBuilderState.prototype, "updateField", null);
|
|
695
|
+
__decorate([
|
|
696
|
+
Action(DeleteField)
|
|
697
|
+
], FormBuilderState.prototype, "deleteField", null);
|
|
698
|
+
__decorate([
|
|
699
|
+
Action(ReorderFields)
|
|
700
|
+
], FormBuilderState.prototype, "reorderFields", null);
|
|
701
|
+
__decorate([
|
|
702
|
+
Action(MoveField)
|
|
703
|
+
], FormBuilderState.prototype, "moveField", null);
|
|
704
|
+
__decorate([
|
|
705
|
+
Selector()
|
|
706
|
+
], FormBuilderState, "getState", null);
|
|
707
|
+
__decorate([
|
|
708
|
+
Selector()
|
|
709
|
+
], FormBuilderState, "getFormConfiguration", null);
|
|
710
|
+
__decorate([
|
|
711
|
+
Selector()
|
|
712
|
+
], FormBuilderState, "getSections", null);
|
|
713
|
+
__decorate([
|
|
714
|
+
Selector()
|
|
715
|
+
], FormBuilderState, "getModuleType", null);
|
|
716
|
+
__decorate([
|
|
717
|
+
Selector()
|
|
718
|
+
], FormBuilderState, "getModuleId", null);
|
|
719
|
+
__decorate([
|
|
720
|
+
Selector()
|
|
721
|
+
], FormBuilderState, "getProperties", null);
|
|
722
|
+
FormBuilderState = __decorate([
|
|
723
|
+
State({
|
|
724
|
+
name: 'formBuilder',
|
|
725
|
+
defaults: DEFAULT_STATE,
|
|
726
|
+
})
|
|
727
|
+
], FormBuilderState);
|
|
728
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderState, decorators: [{
|
|
729
|
+
type: Injectable
|
|
730
|
+
}], propDecorators: { setModuleInfo: [], resetState: [], setProperties: [], getFormConfiguration: [], resetFormConfiguration: [], addSection: [], updateSection: [], deleteSection: [], addField: [], updateField: [], deleteField: [], reorderFields: [], moveField: [] } });
|
|
731
|
+
|
|
732
|
+
class FormBuilderFacade {
|
|
733
|
+
store = inject(Store);
|
|
734
|
+
// ============================================================================
|
|
735
|
+
// State Selectors
|
|
736
|
+
// ============================================================================
|
|
737
|
+
stateSignal = select(FormBuilderState.getState);
|
|
738
|
+
formConfiguration = select(FormBuilderState.getFormConfiguration);
|
|
739
|
+
sections = select(FormBuilderState.getSections);
|
|
740
|
+
properties = select(FormBuilderState.getProperties);
|
|
741
|
+
moduleType = select(FormBuilderState.getModuleType);
|
|
742
|
+
moduleId = select(FormBuilderState.getModuleId);
|
|
743
|
+
// ============================================================================
|
|
744
|
+
// Loading Signals
|
|
745
|
+
// ============================================================================
|
|
746
|
+
isLoadingFormConfiguration = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.GetFormConfiguration), ...(ngDevMode ? [{ debugName: "isLoadingFormConfiguration" }] : []));
|
|
747
|
+
isResettingFormConfiguration = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.ResetFormConfiguration), ...(ngDevMode ? [{ debugName: "isResettingFormConfiguration" }] : []));
|
|
748
|
+
isAddingSection = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.AddSection), ...(ngDevMode ? [{ debugName: "isAddingSection" }] : []));
|
|
749
|
+
isUpdatingSection = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.UpdateSection), ...(ngDevMode ? [{ debugName: "isUpdatingSection" }] : []));
|
|
750
|
+
isDeletingSection = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.DeleteSection), ...(ngDevMode ? [{ debugName: "isDeletingSection" }] : []));
|
|
751
|
+
isAddingField = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.AddField), ...(ngDevMode ? [{ debugName: "isAddingField" }] : []));
|
|
752
|
+
isUpdatingField = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.UpdateField), ...(ngDevMode ? [{ debugName: "isUpdatingField" }] : []));
|
|
753
|
+
isDeletingField = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.DeleteField), ...(ngDevMode ? [{ debugName: "isDeletingField" }] : []));
|
|
754
|
+
isMovingField = computed(() => this.stateSignal().loadingActive.includes(FormBuilderActionKey.MoveField), ...(ngDevMode ? [{ debugName: "isMovingField" }] : []));
|
|
755
|
+
// ============================================================================
|
|
756
|
+
// Error Signals
|
|
757
|
+
// ============================================================================
|
|
758
|
+
formConfigurationError = computed(() => this.stateSignal().errors[FormBuilderActionKey.GetFormConfiguration] ??
|
|
759
|
+
null, ...(ngDevMode ? [{ debugName: "formConfigurationError" }] : []));
|
|
760
|
+
sectionError = computed(() => {
|
|
761
|
+
const errors = this.stateSignal().errors;
|
|
762
|
+
return (errors[FormBuilderActionKey.AddSection] ??
|
|
763
|
+
errors[FormBuilderActionKey.UpdateSection] ??
|
|
764
|
+
errors[FormBuilderActionKey.DeleteSection] ??
|
|
765
|
+
null);
|
|
766
|
+
}, ...(ngDevMode ? [{ debugName: "sectionError" }] : []));
|
|
767
|
+
fieldError = computed(() => {
|
|
768
|
+
const errors = this.stateSignal().errors;
|
|
769
|
+
return (errors[FormBuilderActionKey.AddField] ??
|
|
770
|
+
errors[FormBuilderActionKey.UpdateField] ??
|
|
771
|
+
errors[FormBuilderActionKey.DeleteField] ??
|
|
772
|
+
errors[FormBuilderActionKey.MoveField] ??
|
|
773
|
+
null);
|
|
774
|
+
}, ...(ngDevMode ? [{ debugName: "fieldError" }] : []));
|
|
775
|
+
// ============================================================================
|
|
776
|
+
// Module Configuration Dispatchers
|
|
777
|
+
// ============================================================================
|
|
778
|
+
setModuleInfo(moduleType, moduleId, parentModuleType, parentModuleId, parentPath) {
|
|
779
|
+
return this.store.dispatch(new SetModuleInfo(moduleType, moduleId, parentModuleType, parentModuleId, parentPath));
|
|
780
|
+
}
|
|
781
|
+
resetState() {
|
|
782
|
+
return this.store.dispatch(new ResetFormBuilderState());
|
|
783
|
+
}
|
|
784
|
+
setProperties(properties) {
|
|
785
|
+
return this.store.dispatch(new SetProperties(properties));
|
|
786
|
+
}
|
|
787
|
+
// ============================================================================
|
|
788
|
+
// Form Configuration Dispatchers
|
|
789
|
+
// ============================================================================
|
|
790
|
+
getFormConfiguration() {
|
|
791
|
+
return this.store.dispatch(new GetFormConfiguration());
|
|
792
|
+
}
|
|
793
|
+
resetFormConfiguration() {
|
|
794
|
+
return this.store.dispatch(new ResetFormConfiguration());
|
|
795
|
+
}
|
|
796
|
+
// ============================================================================
|
|
797
|
+
// Section Dispatchers
|
|
798
|
+
// ============================================================================
|
|
799
|
+
addSection(payload) {
|
|
800
|
+
return this.store.dispatch(new AddSection(payload));
|
|
801
|
+
}
|
|
802
|
+
updateSection(sectionId, payload) {
|
|
803
|
+
return this.store.dispatch(new UpdateSection(sectionId, payload));
|
|
804
|
+
}
|
|
805
|
+
deleteSection(sectionId) {
|
|
806
|
+
return this.store.dispatch(new DeleteSection(sectionId));
|
|
807
|
+
}
|
|
808
|
+
// ============================================================================
|
|
809
|
+
// Field Dispatchers
|
|
810
|
+
// ============================================================================
|
|
811
|
+
addField(sectionId, payload) {
|
|
812
|
+
return this.store.dispatch(new AddField(sectionId, payload));
|
|
813
|
+
}
|
|
814
|
+
updateField(sectionId, fieldId, payload) {
|
|
815
|
+
return this.store.dispatch(new UpdateField(sectionId, fieldId, payload));
|
|
816
|
+
}
|
|
817
|
+
deleteField(sectionId, fieldId) {
|
|
818
|
+
return this.store.dispatch(new DeleteField(sectionId, fieldId));
|
|
819
|
+
}
|
|
820
|
+
reorderFields(sectionId, payload) {
|
|
821
|
+
return this.store.dispatch(new ReorderFields(sectionId, payload));
|
|
822
|
+
}
|
|
823
|
+
moveField(sectionId, fieldId, payload) {
|
|
824
|
+
return this.store.dispatch(new MoveField(sectionId, fieldId, payload));
|
|
825
|
+
}
|
|
826
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
827
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderFacade, providedIn: 'root' });
|
|
828
|
+
}
|
|
829
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilderFacade, decorators: [{
|
|
830
|
+
type: Injectable,
|
|
831
|
+
args: [{ providedIn: 'root' }]
|
|
832
|
+
}] });
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Condition Formula Constants
|
|
836
|
+
* Static functions and operators for condition formulas (Show/Hide/Enable/Disable)
|
|
837
|
+
*/
|
|
838
|
+
/**
|
|
839
|
+
* Functions for field conditions
|
|
840
|
+
*/
|
|
841
|
+
const CONDITION_FUNCTION_CATEGORIES = [
|
|
842
|
+
{
|
|
843
|
+
name: 'Logic',
|
|
844
|
+
displayName: 'Field Visibility',
|
|
845
|
+
functions: [
|
|
846
|
+
{
|
|
847
|
+
name: 'SHOW_IF',
|
|
848
|
+
category: 'Logic',
|
|
849
|
+
description: 'Show this field when condition is true',
|
|
850
|
+
signature: 'SHOW_IF(condition)',
|
|
851
|
+
parameters: [
|
|
852
|
+
{
|
|
853
|
+
name: 'condition',
|
|
854
|
+
type: 'boolean',
|
|
855
|
+
description: 'When true, field is visible',
|
|
856
|
+
required: true,
|
|
857
|
+
},
|
|
858
|
+
],
|
|
859
|
+
returnType: 'boolean',
|
|
860
|
+
examples: [
|
|
861
|
+
'SHOW_IF(@Status == "Active")',
|
|
862
|
+
'SHOW_IF(@Type == "Custom")',
|
|
863
|
+
],
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
name: 'HIDE_IF',
|
|
867
|
+
category: 'Logic',
|
|
868
|
+
description: 'Hide this field when condition is true',
|
|
869
|
+
signature: 'HIDE_IF(condition)',
|
|
870
|
+
parameters: [
|
|
871
|
+
{
|
|
872
|
+
name: 'condition',
|
|
873
|
+
type: 'boolean',
|
|
874
|
+
description: 'When true, field is hidden',
|
|
875
|
+
required: true,
|
|
876
|
+
},
|
|
877
|
+
],
|
|
878
|
+
returnType: 'boolean',
|
|
879
|
+
examples: ['HIDE_IF(@Status == "Closed")', 'HIDE_IF(ISNULL(@Parent))'],
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
name: 'DISABLE_IF',
|
|
883
|
+
category: 'Logic',
|
|
884
|
+
description: 'Disable this field when condition is true',
|
|
885
|
+
signature: 'DISABLE_IF(condition)',
|
|
886
|
+
parameters: [
|
|
887
|
+
{
|
|
888
|
+
name: 'condition',
|
|
889
|
+
type: 'boolean',
|
|
890
|
+
description: 'When true, field is disabled',
|
|
891
|
+
required: true,
|
|
892
|
+
},
|
|
893
|
+
],
|
|
894
|
+
returnType: 'boolean',
|
|
895
|
+
examples: [
|
|
896
|
+
'DISABLE_IF(@IsLocked == true)',
|
|
897
|
+
'DISABLE_IF(@Status == "Approved")',
|
|
898
|
+
],
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
name: 'ENABLE_IF',
|
|
902
|
+
category: 'Logic',
|
|
903
|
+
description: 'Enable this field when condition is true',
|
|
904
|
+
signature: 'ENABLE_IF(condition)',
|
|
905
|
+
parameters: [
|
|
906
|
+
{
|
|
907
|
+
name: 'condition',
|
|
908
|
+
type: 'boolean',
|
|
909
|
+
description: 'When true, field is enabled',
|
|
910
|
+
required: true,
|
|
911
|
+
},
|
|
912
|
+
],
|
|
913
|
+
returnType: 'boolean',
|
|
914
|
+
examples: [
|
|
915
|
+
'ENABLE_IF(@Status == "Draft")',
|
|
916
|
+
'ENABLE_IF(@CanEdit == true)',
|
|
917
|
+
],
|
|
918
|
+
},
|
|
919
|
+
],
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
name: 'Aggregation',
|
|
923
|
+
displayName: 'Logic Helpers',
|
|
924
|
+
functions: [
|
|
925
|
+
{
|
|
926
|
+
name: 'AND',
|
|
927
|
+
category: 'Aggregation',
|
|
928
|
+
description: 'Returns true if all conditions are true',
|
|
929
|
+
signature: 'AND(condition1, condition2)',
|
|
930
|
+
parameters: [
|
|
931
|
+
{
|
|
932
|
+
name: 'condition1',
|
|
933
|
+
type: 'boolean',
|
|
934
|
+
description: 'First condition',
|
|
935
|
+
required: true,
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
name: 'condition2',
|
|
939
|
+
type: 'boolean',
|
|
940
|
+
description: 'Second condition',
|
|
941
|
+
required: true,
|
|
942
|
+
},
|
|
943
|
+
],
|
|
944
|
+
returnType: 'boolean',
|
|
945
|
+
examples: ['AND(@Status == "Active", @Priority > 0)'],
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
name: 'OR',
|
|
949
|
+
category: 'Aggregation',
|
|
950
|
+
description: 'Returns true if any condition is true',
|
|
951
|
+
signature: 'OR(condition1, condition2)',
|
|
952
|
+
parameters: [
|
|
953
|
+
{
|
|
954
|
+
name: 'condition1',
|
|
955
|
+
type: 'boolean',
|
|
956
|
+
description: 'First condition',
|
|
957
|
+
required: true,
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
name: 'condition2',
|
|
961
|
+
type: 'boolean',
|
|
962
|
+
description: 'Second condition',
|
|
963
|
+
required: true,
|
|
964
|
+
},
|
|
965
|
+
],
|
|
966
|
+
returnType: 'boolean',
|
|
967
|
+
examples: ['OR(@Status == "Active", @Status == "Pending")'],
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
name: 'NOT',
|
|
971
|
+
category: 'Aggregation',
|
|
972
|
+
description: 'Returns the opposite of the condition',
|
|
973
|
+
signature: 'NOT(condition)',
|
|
974
|
+
parameters: [
|
|
975
|
+
{
|
|
976
|
+
name: 'condition',
|
|
977
|
+
type: 'boolean',
|
|
978
|
+
description: 'The condition to negate',
|
|
979
|
+
required: true,
|
|
980
|
+
},
|
|
981
|
+
],
|
|
982
|
+
returnType: 'boolean',
|
|
983
|
+
examples: ['NOT(@IsCompleted)'],
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
name: 'ISNULL',
|
|
987
|
+
category: 'Aggregation',
|
|
988
|
+
description: 'Returns true if value is null or empty',
|
|
989
|
+
signature: 'ISNULL(value)',
|
|
990
|
+
parameters: [
|
|
991
|
+
{
|
|
992
|
+
name: 'value',
|
|
993
|
+
type: 'any',
|
|
994
|
+
description: 'The value to check',
|
|
995
|
+
required: true,
|
|
996
|
+
},
|
|
997
|
+
],
|
|
998
|
+
returnType: 'boolean',
|
|
999
|
+
examples: ['ISNULL(@Description)'],
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
name: 'CONTAINS',
|
|
1003
|
+
category: 'Aggregation',
|
|
1004
|
+
description: 'Returns true if text contains the search string',
|
|
1005
|
+
signature: 'CONTAINS(text, search)',
|
|
1006
|
+
parameters: [
|
|
1007
|
+
{
|
|
1008
|
+
name: 'text',
|
|
1009
|
+
type: 'string',
|
|
1010
|
+
description: 'The text to search in',
|
|
1011
|
+
required: true,
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
name: 'search',
|
|
1015
|
+
type: 'string',
|
|
1016
|
+
description: 'The string to search for',
|
|
1017
|
+
required: true,
|
|
1018
|
+
},
|
|
1019
|
+
],
|
|
1020
|
+
returnType: 'boolean',
|
|
1021
|
+
examples: ['CONTAINS(@Name, "Project")'],
|
|
1022
|
+
},
|
|
1023
|
+
],
|
|
1024
|
+
},
|
|
1025
|
+
];
|
|
1026
|
+
/**
|
|
1027
|
+
* Operators for conditions
|
|
1028
|
+
*/
|
|
1029
|
+
const CONDITION_OPERATORS = [
|
|
1030
|
+
// Comparison
|
|
1031
|
+
{
|
|
1032
|
+
symbol: '==',
|
|
1033
|
+
name: 'Equal',
|
|
1034
|
+
type: 'comparison',
|
|
1035
|
+
description: 'Equal to',
|
|
1036
|
+
precedence: 3,
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
symbol: '!=',
|
|
1040
|
+
name: 'Not Equal',
|
|
1041
|
+
type: 'comparison',
|
|
1042
|
+
description: 'Not equal to',
|
|
1043
|
+
precedence: 3,
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
symbol: '>',
|
|
1047
|
+
name: 'Greater Than',
|
|
1048
|
+
type: 'comparison',
|
|
1049
|
+
description: 'Greater than',
|
|
1050
|
+
precedence: 4,
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
symbol: '<',
|
|
1054
|
+
name: 'Less Than',
|
|
1055
|
+
type: 'comparison',
|
|
1056
|
+
description: 'Less than',
|
|
1057
|
+
precedence: 4,
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
symbol: '>=',
|
|
1061
|
+
name: 'Greater or Equal',
|
|
1062
|
+
type: 'comparison',
|
|
1063
|
+
description: 'Greater than or equal',
|
|
1064
|
+
precedence: 4,
|
|
1065
|
+
},
|
|
1066
|
+
{
|
|
1067
|
+
symbol: '<=',
|
|
1068
|
+
name: 'Less or Equal',
|
|
1069
|
+
type: 'comparison',
|
|
1070
|
+
description: 'Less than or equal',
|
|
1071
|
+
precedence: 4,
|
|
1072
|
+
},
|
|
1073
|
+
// Logical
|
|
1074
|
+
{
|
|
1075
|
+
symbol: '&&',
|
|
1076
|
+
name: 'And',
|
|
1077
|
+
type: 'logical',
|
|
1078
|
+
description: 'Logical AND',
|
|
1079
|
+
precedence: 2,
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
symbol: '||',
|
|
1083
|
+
name: 'Or',
|
|
1084
|
+
type: 'logical',
|
|
1085
|
+
description: 'Logical OR',
|
|
1086
|
+
precedence: 1,
|
|
1087
|
+
},
|
|
1088
|
+
];
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Field Conditions Dialog
|
|
1092
|
+
*
|
|
1093
|
+
* Allows users to define conditional display formulas for form fields.
|
|
1094
|
+
* Uses FormulaToolbar + FormulaEditor directly for a visual formula editing experience.
|
|
1095
|
+
*/
|
|
1096
|
+
class FBFieldConditions {
|
|
1097
|
+
modalService = inject(ModalService);
|
|
1098
|
+
ref = inject(ModalRef);
|
|
1099
|
+
// Inputs
|
|
1100
|
+
/** Initial formula (JSON string from backend) */
|
|
1101
|
+
initialFormula = input('', ...(ngDevMode ? [{ debugName: "initialFormula" }] : []));
|
|
1102
|
+
/** Available fields from the form builder (other fields that can be referenced) */
|
|
1103
|
+
availableFields = input([], ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
|
|
1104
|
+
// UI State
|
|
1105
|
+
submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
|
|
1106
|
+
// Form control for the formula editor (tokens array)
|
|
1107
|
+
formulaControl = new FormControl([]);
|
|
1108
|
+
// View child for the editor
|
|
1109
|
+
editorRef = viewChild('editor', ...(ngDevMode ? [{ debugName: "editorRef" }] : []));
|
|
1110
|
+
// Condition-specific functions and operators
|
|
1111
|
+
functionCategories = CONDITION_FUNCTION_CATEGORIES;
|
|
1112
|
+
operators = CONDITION_OPERATORS;
|
|
1113
|
+
/** Extract property keys for toolbar */
|
|
1114
|
+
propertyKeys = computed(() => this.availableFields().map((f) => f.key), ...(ngDevMode ? [{ debugName: "propertyKeys" }] : []));
|
|
1115
|
+
/** Toolbar labels */
|
|
1116
|
+
toolbarLabels = {
|
|
1117
|
+
functions: 'Functions',
|
|
1118
|
+
properties: 'Fields',
|
|
1119
|
+
operators: 'Operators',
|
|
1120
|
+
noPropertiesAvailable: 'No fields available',
|
|
1121
|
+
};
|
|
1122
|
+
ngOnInit() {
|
|
1123
|
+
// Parse JSON string to tokens
|
|
1124
|
+
const formula = this.initialFormula();
|
|
1125
|
+
if (formula) {
|
|
1126
|
+
try {
|
|
1127
|
+
const tokens = JSON.parse(formula);
|
|
1128
|
+
this.formulaControl.patchValue(tokens);
|
|
1129
|
+
}
|
|
1130
|
+
catch {
|
|
1131
|
+
// Invalid JSON, start with empty
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
/** Handle block insert from toolbar */
|
|
1136
|
+
onBlockInsert(block) {
|
|
1137
|
+
const editor = this.editorRef();
|
|
1138
|
+
if (editor) {
|
|
1139
|
+
editor.addBlock(block);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
onSave() {
|
|
1143
|
+
const tokens = this.formulaControl.value ?? [];
|
|
1144
|
+
const formula = tokens.length > 0 ? JSON.stringify(tokens) : '';
|
|
1145
|
+
this.ref.close({ saved: true, conditionalDisplayFormula: formula });
|
|
1146
|
+
}
|
|
1147
|
+
onCancel() {
|
|
1148
|
+
this.ref.close({ saved: false });
|
|
1149
|
+
}
|
|
1150
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldConditions, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1151
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.0.3", type: FBFieldConditions, isStandalone: true, selector: "mt-fb-field-conditions", inputs: { initialFormula: { classPropertyName: "initialFormula", publicName: "initialFormula", isSignal: true, isRequired: false, transformFunction: null }, availableFields: { classPropertyName: "availableFields", publicName: "availableFields", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-3\">\r\n <!-- Formula Toolbar -->\r\n <mt-formula-toolbar\r\n [knownProperties]=\"propertyKeys()\"\r\n [functionCategories]=\"functionCategories\"\r\n [operators]=\"operators\"\r\n [labels]=\"toolbarLabels\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n />\r\n <!-- Formula Editor -->\r\n <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('build-condition-formula')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { 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: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: FormulaToolbar, selector: "mt-formula-toolbar", inputs: ["knownProperties", "functionCategories", "operators", "initialTab", "searchPlaceholder", "labels"], outputs: ["onBlockInsert", "onTabChange"] }, { kind: "component", type: FormulaEditor, selector: "mt-formula-editor", inputs: ["placeholder", "initialTokens", "disabled"], outputs: ["formulaChange", "tokensChange", "onBlur", "onFocus"] }] });
|
|
1152
|
+
}
|
|
1153
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldConditions, decorators: [{
|
|
1154
|
+
type: Component,
|
|
1155
|
+
args: [{ selector: 'mt-fb-field-conditions', standalone: true, imports: [
|
|
1156
|
+
TranslocoDirective,
|
|
1157
|
+
ReactiveFormsModule,
|
|
1158
|
+
Button,
|
|
1159
|
+
FormulaToolbar,
|
|
1160
|
+
FormulaEditor,
|
|
1161
|
+
], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-3\">\r\n <!-- Formula Toolbar -->\r\n <mt-formula-toolbar\r\n [knownProperties]=\"propertyKeys()\"\r\n [functionCategories]=\"functionCategories\"\r\n [operators]=\"operators\"\r\n [labels]=\"toolbarLabels\"\r\n (onBlockInsert)=\"onBlockInsert($event)\"\r\n />\r\n <!-- Formula Editor -->\r\n <mt-formula-editor\r\n #editor\r\n [placeholder]=\"t('build-condition-formula')\"\r\n [formControl]=\"formulaControl\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onCancel()\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n />\r\n </div>\r\n</ng-container>\r\n" }]
|
|
1162
|
+
}], propDecorators: { initialFormula: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFormula", required: false }] }], availableFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableFields", required: false }] }], editorRef: [{ type: i0.ViewChild, args: ['editor', { isSignal: true }] }] } });
|
|
1163
|
+
|
|
1164
|
+
class FBFieldForm {
|
|
1165
|
+
transloco = inject(TranslocoService);
|
|
1166
|
+
modalService = inject(ModalService);
|
|
1167
|
+
ref = inject(ModalRef);
|
|
1168
|
+
confirmationService = inject(ConfirmationService);
|
|
1169
|
+
facade = inject(FormBuilderFacade);
|
|
1170
|
+
// Inputs
|
|
1171
|
+
sectionId = input('', ...(ngDevMode ? [{ debugName: "sectionId" }] : []));
|
|
1172
|
+
initialData = input(null, ...(ngDevMode ? [{ debugName: "initialData" }] : []));
|
|
1173
|
+
/** All sections with enriched fields (to get available fields for conditions) */
|
|
1174
|
+
allSections = input([], ...(ngDevMode ? [{ debugName: "allSections" }] : []));
|
|
1175
|
+
// UI State
|
|
1176
|
+
submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
|
|
1177
|
+
deleting = signal(false, ...(ngDevMode ? [{ debugName: "deleting" }] : []));
|
|
1178
|
+
conditionalDisplayFormula = signal('', ...(ngDevMode ? [{ debugName: "conditionalDisplayFormula" }] : []));
|
|
1179
|
+
conditionsDialogRef;
|
|
1180
|
+
/**
|
|
1181
|
+
* Compute available fields for condition formula
|
|
1182
|
+
* Excludes the current field being edited
|
|
1183
|
+
*/
|
|
1184
|
+
availableFields = computed(() => {
|
|
1185
|
+
const sections = this.allSections();
|
|
1186
|
+
const currentField = this.initialData();
|
|
1187
|
+
const fields = [];
|
|
1188
|
+
for (const section of sections) {
|
|
1189
|
+
for (const field of section.fields) {
|
|
1190
|
+
// Exclude the current field being edited
|
|
1191
|
+
if (currentField && field.id === currentField.id)
|
|
1192
|
+
continue;
|
|
1193
|
+
fields.push({
|
|
1194
|
+
key: field.name,
|
|
1195
|
+
name: field.name,
|
|
1196
|
+
type: field.type,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return fields;
|
|
1201
|
+
}, ...(ngDevMode ? [{ debugName: "availableFields" }] : []));
|
|
1202
|
+
// Form
|
|
1203
|
+
formControl = new FormControl();
|
|
1204
|
+
showHideControl = new FormControl(false);
|
|
1205
|
+
formConfig = {
|
|
1206
|
+
sections: [
|
|
1207
|
+
{
|
|
1208
|
+
key: 'section-form',
|
|
1209
|
+
type: 'none',
|
|
1210
|
+
columns: 12,
|
|
1211
|
+
order: 1,
|
|
1212
|
+
fields: [
|
|
1213
|
+
{
|
|
1214
|
+
key: 'isRequired',
|
|
1215
|
+
label: this.transloco.translate('formBuilder.is-required'),
|
|
1216
|
+
type: 'toggle',
|
|
1217
|
+
toggleShape: 'card',
|
|
1218
|
+
colSpan: 12,
|
|
1219
|
+
order: 1,
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
key: 'hiddenInCreation',
|
|
1223
|
+
label: this.transloco.translate('formBuilder.hidden-in-creation'),
|
|
1224
|
+
type: 'toggle',
|
|
1225
|
+
toggleShape: 'card',
|
|
1226
|
+
colSpan: 12,
|
|
1227
|
+
order: 2,
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
key: 'hiddenInEditForm',
|
|
1231
|
+
label: this.transloco.translate('formBuilder.hidden-in-edit-form'),
|
|
1232
|
+
type: 'toggle',
|
|
1233
|
+
toggleShape: 'card',
|
|
1234
|
+
colSpan: 12,
|
|
1235
|
+
order: 3,
|
|
1236
|
+
},
|
|
1237
|
+
new RadioCardsFieldConfig({
|
|
1238
|
+
key: 'size',
|
|
1239
|
+
label: this.transloco.translate('formBuilder.size'),
|
|
1240
|
+
placeholder: this.transloco.translate('formBuilder.size'),
|
|
1241
|
+
options: [
|
|
1242
|
+
{ id: 's', name: 'S' },
|
|
1243
|
+
{ id: 'm', name: 'M' },
|
|
1244
|
+
{ id: 'l', name: 'L' },
|
|
1245
|
+
],
|
|
1246
|
+
order: 4,
|
|
1247
|
+
size: 'small',
|
|
1248
|
+
}),
|
|
1249
|
+
],
|
|
1250
|
+
},
|
|
1251
|
+
],
|
|
1252
|
+
};
|
|
1253
|
+
constructor() {
|
|
1254
|
+
effect(() => {
|
|
1255
|
+
const data = this.initialData();
|
|
1256
|
+
if (data) {
|
|
1257
|
+
const widthToSize = {
|
|
1258
|
+
'25': 's',
|
|
1259
|
+
'50': 'm',
|
|
1260
|
+
'100': 'l',
|
|
1261
|
+
};
|
|
1262
|
+
this.formControl.patchValue({
|
|
1263
|
+
isRequired: data.isRequired ?? false,
|
|
1264
|
+
hiddenInCreation: data.hiddenInCreation ?? false,
|
|
1265
|
+
hiddenInEditForm: data.hiddenInEditForm ?? false,
|
|
1266
|
+
size: widthToSize[data.width] ?? 'l',
|
|
1267
|
+
});
|
|
1268
|
+
// Set show/hide toggle based on existing value
|
|
1269
|
+
this.showHideControl.patchValue(data.showConditionalDisplayFormula ?? false);
|
|
1270
|
+
this.conditionalDisplayFormula.set(data.conditionalDisplayFormula ?? '');
|
|
1271
|
+
}
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
onSave() {
|
|
1275
|
+
if (this.formControl.invalid)
|
|
1276
|
+
return;
|
|
1277
|
+
const formValue = this.formControl.value;
|
|
1278
|
+
const field = this.initialData();
|
|
1279
|
+
const sectionId = this.sectionId();
|
|
1280
|
+
if (!field || !sectionId)
|
|
1281
|
+
return;
|
|
1282
|
+
const widthMap = {
|
|
1283
|
+
s: '25',
|
|
1284
|
+
m: '50',
|
|
1285
|
+
l: '100',
|
|
1286
|
+
};
|
|
1287
|
+
const payload = {
|
|
1288
|
+
width: widthMap[formValue.size] ?? '100',
|
|
1289
|
+
hiddenInCreation: formValue.hiddenInCreation ?? false,
|
|
1290
|
+
hiddenInEditForm: formValue.hiddenInEditForm ?? false,
|
|
1291
|
+
isRequired: formValue.isRequired ?? false,
|
|
1292
|
+
showConditionalDisplayFormula: this.showHideControl.value ?? false,
|
|
1293
|
+
conditionalDisplayFormula: this.showHideControl.value
|
|
1294
|
+
? this.conditionalDisplayFormula()
|
|
1295
|
+
: null,
|
|
1296
|
+
};
|
|
1297
|
+
this.submitting.set(true);
|
|
1298
|
+
this.facade.updateField(sectionId, field.id, payload).subscribe({
|
|
1299
|
+
next: () => this.ref.close(true),
|
|
1300
|
+
error: () => this.submitting.set(false),
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
onCancel() {
|
|
1304
|
+
this.ref.close(false);
|
|
1305
|
+
}
|
|
1306
|
+
onSetConditions() {
|
|
1307
|
+
this.conditionsDialogRef = this.modalService.openModal(FBFieldConditions, 'drawer', {
|
|
1308
|
+
header: this.transloco.translate('formBuilder.set-conditions'),
|
|
1309
|
+
styleClass: '!w-[calc(100%-25rem)] !absolute ',
|
|
1310
|
+
position: 'start',
|
|
1311
|
+
modal: true,
|
|
1312
|
+
dismissible: true,
|
|
1313
|
+
appendTo: '#page-content',
|
|
1314
|
+
inputValues: {
|
|
1315
|
+
initialFormula: this.conditionalDisplayFormula(),
|
|
1316
|
+
availableFields: this.availableFields(),
|
|
1317
|
+
},
|
|
1318
|
+
});
|
|
1319
|
+
this.conditionsDialogRef.onClose.subscribe((result) => {
|
|
1320
|
+
if (result?.saved) {
|
|
1321
|
+
this.conditionalDisplayFormula.set(result.conditionalDisplayFormula ?? '');
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
onDelete(event) {
|
|
1326
|
+
const field = this.initialData();
|
|
1327
|
+
const sectionId = this.sectionId();
|
|
1328
|
+
if (!field || !sectionId)
|
|
1329
|
+
return;
|
|
1330
|
+
this.confirmationService.confirmDelete({
|
|
1331
|
+
event,
|
|
1332
|
+
type: 'popup',
|
|
1333
|
+
accept: () => {
|
|
1334
|
+
this.deleting.set(true);
|
|
1335
|
+
this.facade.deleteField(sectionId, field.id).subscribe({
|
|
1336
|
+
next: () => this.ref.close(true),
|
|
1337
|
+
error: () => this.deleting.set(false),
|
|
1338
|
+
});
|
|
1339
|
+
},
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1343
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FBFieldForm, isStandalone: true, selector: "mt-fb-field-form", inputs: { sectionId: { classPropertyName: "sectionId", publicName: "sectionId", isSignal: true, isRequired: false, transformFunction: null }, initialData: { classPropertyName: "initialData", publicName: "initialData", isSignal: true, isRequired: false, transformFunction: null }, allSections: { classPropertyName: "allSections", publicName: "allSections", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-4\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\">\r\n </mt-dynamic-form>\r\n\r\n <!-- Show/Hide Toggle with Set Conditions -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-hide')\"\r\n [descriptionCard]=\"t('show-hide-description')\"\r\n icon=\"general.eye\"\r\n [formControl]=\"showHideControl\"\r\n >\r\n <ng-template #toggleCardBottom>\r\n @if (showHideControl.value) {\r\n <div class=\"mt-3\">\r\n <mt-button\r\n [label]=\"t('set-conditions')\"\r\n size=\"small\"\r\n (onClick)=\"onSetConditions()\"\r\n ></mt-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </mt-toggle-field>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n @if (initialData()) {\r\n <mt-button\r\n [tooltip]=\"t('delete')\"\r\n severity=\"danger\"\r\n outlined\r\n icon=\"general.trash-01\"\r\n [loading]=\"deleting()\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onDelete($event)\"\r\n class=\"me-auto\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting() || deleting()\"\r\n (onClick)=\"onCancel()\"\r\n >\r\n </mt-button>\r\n <mt-button\r\n [disabled]=\"!formControl.valid || deleting()\"\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n >\r\n </mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { 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: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }] });
|
|
1344
|
+
}
|
|
1345
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBFieldForm, decorators: [{
|
|
1346
|
+
type: Component,
|
|
1347
|
+
args: [{ selector: 'mt-fb-field-form', standalone: true, imports: [
|
|
1348
|
+
TranslocoDirective,
|
|
1349
|
+
ReactiveFormsModule,
|
|
1350
|
+
DynamicForm,
|
|
1351
|
+
Button,
|
|
1352
|
+
ToggleField,
|
|
1353
|
+
], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-4\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\">\r\n </mt-dynamic-form>\r\n\r\n <!-- Show/Hide Toggle with Set Conditions -->\r\n <mt-toggle-field\r\n toggleShape=\"card\"\r\n [label]=\"t('show-hide')\"\r\n [descriptionCard]=\"t('show-hide-description')\"\r\n icon=\"general.eye\"\r\n [formControl]=\"showHideControl\"\r\n >\r\n <ng-template #toggleCardBottom>\r\n @if (showHideControl.value) {\r\n <div class=\"mt-3\">\r\n <mt-button\r\n [label]=\"t('set-conditions')\"\r\n size=\"small\"\r\n (onClick)=\"onSetConditions()\"\r\n ></mt-button>\r\n </div>\r\n }\r\n </ng-template>\r\n </mt-toggle-field>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n @if (initialData()) {\r\n <mt-button\r\n [tooltip]=\"t('delete')\"\r\n severity=\"danger\"\r\n outlined\r\n icon=\"general.trash-01\"\r\n [loading]=\"deleting()\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onDelete($event)\"\r\n class=\"me-auto\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting() || deleting()\"\r\n (onClick)=\"onCancel()\"\r\n >\r\n </mt-button>\r\n <mt-button\r\n [disabled]=\"!formControl.valid || deleting()\"\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n >\r\n </mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
|
|
1354
|
+
}], ctorParameters: () => [], propDecorators: { sectionId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionId", required: false }] }], initialData: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialData", required: false }] }], allSections: [{ type: i0.Input, args: [{ isSignal: true, alias: "allSections", required: false }] }] } });
|
|
1355
|
+
|
|
1356
|
+
class FBSectionForm {
|
|
1357
|
+
transloco = inject(TranslocoService);
|
|
1358
|
+
modalService = inject(ModalService);
|
|
1359
|
+
ref = inject(ModalRef);
|
|
1360
|
+
confirmationService = inject(ConfirmationService);
|
|
1361
|
+
facade = inject(FormBuilderFacade);
|
|
1362
|
+
// Inputs
|
|
1363
|
+
sectionId = input(null, ...(ngDevMode ? [{ debugName: "sectionId" }] : []));
|
|
1364
|
+
initialData = input(null, ...(ngDevMode ? [{ debugName: "initialData" }] : []));
|
|
1365
|
+
sectionsCount = input(0, ...(ngDevMode ? [{ debugName: "sectionsCount" }] : []));
|
|
1366
|
+
// UI State
|
|
1367
|
+
submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
|
|
1368
|
+
deleting = signal(false, ...(ngDevMode ? [{ debugName: "deleting" }] : []));
|
|
1369
|
+
// Form
|
|
1370
|
+
formControl = new FormControl();
|
|
1371
|
+
formConfig = {
|
|
1372
|
+
sections: [
|
|
1373
|
+
{
|
|
1374
|
+
key: 'section-form',
|
|
1375
|
+
type: 'none',
|
|
1376
|
+
columns: 12,
|
|
1377
|
+
order: 1,
|
|
1378
|
+
fields: [
|
|
1379
|
+
{
|
|
1380
|
+
key: 'name-ar',
|
|
1381
|
+
label: this.transloco.translate('formBuilder.name-ar'),
|
|
1382
|
+
type: 'text',
|
|
1383
|
+
placeholder: this.transloco.translate('formBuilder.name-ar'),
|
|
1384
|
+
colSpan: 12,
|
|
1385
|
+
order: 1,
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
key: 'name-en',
|
|
1389
|
+
label: this.transloco.translate('formBuilder.name-en'),
|
|
1390
|
+
type: 'text',
|
|
1391
|
+
placeholder: this.transloco.translate('formBuilder.name-en'),
|
|
1392
|
+
colSpan: 12,
|
|
1393
|
+
order: 2,
|
|
1394
|
+
},
|
|
1395
|
+
],
|
|
1396
|
+
},
|
|
1397
|
+
],
|
|
1398
|
+
};
|
|
1399
|
+
constructor() {
|
|
1400
|
+
effect(() => {
|
|
1401
|
+
const data = this.initialData();
|
|
1402
|
+
if (data) {
|
|
1403
|
+
this.formControl.patchValue({
|
|
1404
|
+
'name-ar': data.ar,
|
|
1405
|
+
'name-en': data.en,
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
onSave() {
|
|
1411
|
+
if (this.formControl.invalid)
|
|
1412
|
+
return;
|
|
1413
|
+
const formValue = this.formControl.value;
|
|
1414
|
+
const payload = {
|
|
1415
|
+
name: {
|
|
1416
|
+
ar: formValue['name-ar'],
|
|
1417
|
+
en: formValue['name-en'],
|
|
1418
|
+
},
|
|
1419
|
+
};
|
|
1420
|
+
this.submitting.set(true);
|
|
1421
|
+
const sectionId = this.sectionId();
|
|
1422
|
+
if (sectionId) {
|
|
1423
|
+
// Update existing section
|
|
1424
|
+
this.facade.updateSection(sectionId, payload).subscribe({
|
|
1425
|
+
next: () => this.ref.close(true),
|
|
1426
|
+
error: () => this.submitting.set(false),
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
// Create new section
|
|
1431
|
+
this.facade
|
|
1432
|
+
.addSection({ ...payload, order: this.sectionsCount() })
|
|
1433
|
+
.subscribe({
|
|
1434
|
+
next: () => this.ref.close(true),
|
|
1435
|
+
error: () => this.submitting.set(false),
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
onCancel() {
|
|
1440
|
+
this.ref.close(false);
|
|
1441
|
+
}
|
|
1442
|
+
onDelete(event) {
|
|
1443
|
+
const sectionId = this.sectionId();
|
|
1444
|
+
if (!sectionId)
|
|
1445
|
+
return;
|
|
1446
|
+
this.confirmationService.confirmDelete({
|
|
1447
|
+
event,
|
|
1448
|
+
type: 'popup',
|
|
1449
|
+
accept: () => {
|
|
1450
|
+
this.deleting.set(true);
|
|
1451
|
+
this.facade.deleteSection(sectionId).subscribe({
|
|
1452
|
+
next: () => this.ref.close(true),
|
|
1453
|
+
error: () => this.deleting.set(false),
|
|
1454
|
+
});
|
|
1455
|
+
},
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBSectionForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1459
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FBSectionForm, isStandalone: true, selector: "mt-fb-section-form", inputs: { sectionId: { classPropertyName: "sectionId", publicName: "sectionId", isSignal: true, isRequired: false, transformFunction: null }, initialData: { classPropertyName: "initialData", publicName: "initialData", isSignal: true, isRequired: false, transformFunction: null }, sectionsCount: { classPropertyName: "sectionsCount", publicName: "sectionsCount", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\">\r\n </mt-dynamic-form>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n @if (sectionId()) {\r\n <mt-button\r\n [tooltip]=\"t('delete')\"\r\n severity=\"danger\"\r\n outlined\r\n icon=\"general.trash-01\"\r\n [loading]=\"deleting()\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onDelete($event)\"\r\n class=\"me-auto\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting() || deleting()\"\r\n (onClick)=\"onCancel()\"\r\n >\r\n </mt-button>\r\n <mt-button\r\n [disabled]=\"!formControl.valid || deleting()\"\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n >\r\n </mt-button>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { 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: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }] });
|
|
1460
|
+
}
|
|
1461
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBSectionForm, decorators: [{
|
|
1462
|
+
type: Component,
|
|
1463
|
+
args: [{ selector: 'mt-fb-section-form', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, DynamicForm, Button], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <div class=\"mt-4 h-full overflow-y-auto pb-10\">\r\n <mt-dynamic-form [formConfig]=\"formConfig\" [formControl]=\"formControl\">\r\n </mt-dynamic-form>\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modalService.footerClass\">\r\n @if (sectionId()) {\r\n <mt-button\r\n [tooltip]=\"t('delete')\"\r\n severity=\"danger\"\r\n outlined\r\n icon=\"general.trash-01\"\r\n [loading]=\"deleting()\"\r\n [disabled]=\"submitting()\"\r\n (onClick)=\"onDelete($event)\"\r\n class=\"me-auto\"\r\n ></mt-button>\r\n }\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n severity=\"secondary\"\r\n [disabled]=\"submitting() || deleting()\"\r\n (onClick)=\"onCancel()\"\r\n >\r\n </mt-button>\r\n <mt-button\r\n [disabled]=\"!formControl.valid || deleting()\"\r\n [label]=\"t('save')\"\r\n severity=\"primary\"\r\n [loading]=\"submitting()\"\r\n (onClick)=\"onSave()\"\r\n >\r\n </mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
|
|
1464
|
+
}], ctorParameters: () => [], propDecorators: { sectionId: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionId", required: false }] }], initialData: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialData", required: false }] }], sectionsCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionsCount", required: false }] }] } });
|
|
1465
|
+
|
|
1466
|
+
class FBSection {
|
|
1467
|
+
confirmationService = inject(ConfirmationService);
|
|
1468
|
+
modalService = inject(ModalService);
|
|
1469
|
+
translocoService = inject(TranslocoService);
|
|
1470
|
+
facade = inject(FormBuilderFacade);
|
|
1471
|
+
// Inputs
|
|
1472
|
+
section = input.required(...(ngDevMode ? [{ debugName: "section" }] : []));
|
|
1473
|
+
sectionsCount = input(0, ...(ngDevMode ? [{ debugName: "sectionsCount" }] : []));
|
|
1474
|
+
/** All sections - used to get available fields for condition formulas */
|
|
1475
|
+
allSections = input([], ...(ngDevMode ? [{ debugName: "allSections" }] : []));
|
|
1476
|
+
// Outputs - only keep drag/drop since it needs coordination with parent drop list group
|
|
1477
|
+
onFieldDrop = output();
|
|
1478
|
+
// Computed
|
|
1479
|
+
sectionName = computed(() => {
|
|
1480
|
+
const lang = document.documentElement.lang;
|
|
1481
|
+
const section = this.section();
|
|
1482
|
+
return section.name[lang] ?? section.name['en'];
|
|
1483
|
+
}, ...(ngDevMode ? [{ debugName: "sectionName" }] : []));
|
|
1484
|
+
fields = computed(() => this.section().fields, ...(ngDevMode ? [{ debugName: "fields" }] : []));
|
|
1485
|
+
// UI State
|
|
1486
|
+
expanded = signal(true, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
1487
|
+
// Form groups cache for dynamic fields
|
|
1488
|
+
formGroupsCache = new Map();
|
|
1489
|
+
constructor() {
|
|
1490
|
+
// Cleanup stale form groups when fields change
|
|
1491
|
+
effect(() => {
|
|
1492
|
+
const currentIds = new Set(this.fields().map((f) => f.id));
|
|
1493
|
+
for (const id of this.formGroupsCache.keys()) {
|
|
1494
|
+
if (!currentIds.has(id)) {
|
|
1495
|
+
this.formGroupsCache.delete(id);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
getFormGroup(field) {
|
|
1501
|
+
let fg = this.formGroupsCache.get(field.id);
|
|
1502
|
+
if (fg) {
|
|
1503
|
+
// Check if field name changed
|
|
1504
|
+
if (!fg.contains(field.name)) {
|
|
1505
|
+
const oldKey = Object.keys(fg.controls)[0];
|
|
1506
|
+
const value = oldKey ? fg.get(oldKey)?.value : field.data?.data;
|
|
1507
|
+
fg = new FormGroup({
|
|
1508
|
+
[field.name]: new FormControl(value),
|
|
1509
|
+
});
|
|
1510
|
+
this.formGroupsCache.set(field.id, fg);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
else {
|
|
1514
|
+
fg = new FormGroup({
|
|
1515
|
+
[field.name]: new FormControl(field.data?.data),
|
|
1516
|
+
});
|
|
1517
|
+
this.formGroupsCache.set(field.id, fg);
|
|
1518
|
+
}
|
|
1519
|
+
return fg;
|
|
1520
|
+
}
|
|
1521
|
+
toggleExpanded() {
|
|
1522
|
+
this.expanded.update((v) => !v);
|
|
1523
|
+
}
|
|
1524
|
+
onDrop(event) {
|
|
1525
|
+
this.onFieldDrop.emit(event);
|
|
1526
|
+
}
|
|
1527
|
+
editSection(event) {
|
|
1528
|
+
event.stopPropagation();
|
|
1529
|
+
const section = this.section();
|
|
1530
|
+
this.modalService.openModal(FBSectionForm, 'drawer', {
|
|
1531
|
+
header: this.translocoService.translate('formBuilder.edit-section'),
|
|
1532
|
+
height: '20vw',
|
|
1533
|
+
styleClass: '!w-100 !absolute ',
|
|
1534
|
+
position: 'end',
|
|
1535
|
+
appendTo: '#page-content',
|
|
1536
|
+
modal: true,
|
|
1537
|
+
dismissible: true,
|
|
1538
|
+
inputValues: {
|
|
1539
|
+
sectionId: section.id,
|
|
1540
|
+
initialData: section.name,
|
|
1541
|
+
sectionsCount: this.sectionsCount(),
|
|
1542
|
+
},
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
removeField(event, field) {
|
|
1546
|
+
this.confirmationService.confirmDelete({
|
|
1547
|
+
event,
|
|
1548
|
+
type: 'popup',
|
|
1549
|
+
accept: () => {
|
|
1550
|
+
this.facade.deleteField(this.section().id, field.id);
|
|
1551
|
+
},
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
editField(field) {
|
|
1555
|
+
this.modalService.openModal(FBFieldForm, 'drawer', {
|
|
1556
|
+
header: this.translocoService.translate('formBuilder.field-settings'),
|
|
1557
|
+
height: '20vw',
|
|
1558
|
+
styleClass: '!w-100 !absolute !shadow-none',
|
|
1559
|
+
position: 'end',
|
|
1560
|
+
modal: true,
|
|
1561
|
+
dismissible: true,
|
|
1562
|
+
appendTo: '#page-content',
|
|
1563
|
+
inputValues: {
|
|
1564
|
+
initialData: field,
|
|
1565
|
+
sectionId: this.section().id,
|
|
1566
|
+
allSections: this.allSections(),
|
|
1567
|
+
},
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
getFieldColSpan(field) {
|
|
1571
|
+
switch (field.width) {
|
|
1572
|
+
case '100':
|
|
1573
|
+
return 'col-span-12';
|
|
1574
|
+
case '50':
|
|
1575
|
+
return 'col-span-6';
|
|
1576
|
+
case '25':
|
|
1577
|
+
return 'col-span-3';
|
|
1578
|
+
default:
|
|
1579
|
+
return 'col-span-12';
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
getFieldType(field) {
|
|
1583
|
+
const typeMap = {
|
|
1584
|
+
User: 'select',
|
|
1585
|
+
Text: 'text',
|
|
1586
|
+
LongText: 'editor-field',
|
|
1587
|
+
Percentage: 'slider',
|
|
1588
|
+
Date: 'date',
|
|
1589
|
+
Currency: 'text',
|
|
1590
|
+
Number: 'number',
|
|
1591
|
+
Lookup: 'select',
|
|
1592
|
+
LookupMultiSelect: 'select',
|
|
1593
|
+
Checkbox: 'toggle',
|
|
1594
|
+
InternalModule: 'select',
|
|
1595
|
+
DynamicList: 'select',
|
|
1596
|
+
API: 'select',
|
|
1597
|
+
Time: 'date',
|
|
1598
|
+
Status: 'select',
|
|
1599
|
+
Attachment: 'attachment',
|
|
1600
|
+
EditableListView: 'actionableTable',
|
|
1601
|
+
LookupLog: 'actionableTable',
|
|
1602
|
+
LookupMatrix: 'select',
|
|
1603
|
+
Location: 'select',
|
|
1604
|
+
};
|
|
1605
|
+
return typeMap[field.type] ?? 'text';
|
|
1606
|
+
}
|
|
1607
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1608
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FBSection, isStandalone: true, selector: "mt-fb-section", inputs: { section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: true, transformFunction: null }, sectionsCount: { classPropertyName: "sectionsCount", publicName: "sectionsCount", isSignal: true, isRequired: false, transformFunction: null }, allSections: { classPropertyName: "allSections", publicName: "allSections", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onFieldDrop: "onFieldDrop" }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex flex-col\">\r\n <div\r\n class=\"flex justify-between items-center bg-primary text-primary-contrast p-2 rounded-xl\"\r\n >\r\n <mt-button\r\n size=\"small\"\r\n icon=\"arrow.chevron-down\"\r\n class=\"transition-[rotate]\"\r\n [class.rotate-180]=\"expanded()\"\r\n (onClick)=\"toggleExpanded()\"\r\n ></mt-button>\r\n <span class=\"font-bold\">\r\n {{ sectionName() }}\r\n </span>\r\n <div class=\"flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.edit-02\"\r\n [tooltip]=\"t('edit-section')\"\r\n (onClick)=\"editSection($event)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n @if (expanded()) {\r\n <div\r\n cdkDropList\r\n [id]=\"section().id\"\r\n cdkDropListOrientation=\"mixed\"\r\n [cdkDropListData]=\"fields()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n class=\"grid grid-cols-12 gap-4 relative py-4\"\r\n [class.min-h-27]=\"fields().length === 0\"\r\n >\r\n @for (field of fields(); track field.id) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"field\"\r\n [cdkDragDisabled]=\"field._pending || field._deleting\"\r\n [class]=\"\r\n getFieldColSpan(field) + ' cursor-grab active:cursor-grabbing'\r\n \"\r\n >\r\n <div\r\n *cdkDragPlaceholder\r\n [class]=\"\r\n 'h-full min-h-27 bg-black/10 rounded-2xl ' +\r\n getFieldColSpan(field)\r\n \"\r\n ></div>\r\n @if (field._pending) {\r\n <!-- Skeleton for pending field -->\r\n <mt-card class=\"h-full animate-pulse\">\r\n <div class=\"flex gap-2\">\r\n <div class=\"flex-1 space-y-3\">\r\n <div class=\"h-4 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"h-8 w-8 bg-surface-200 rounded\"></div>\r\n <div class=\"h-8 w-8 bg-surface-200 rounded\"></div>\r\n </div>\r\n </div>\r\n </mt-card>\r\n } @else {\r\n <mt-card\r\n class=\"h-full relative\"\r\n [class.opacity-50]=\"field._deleting\"\r\n >\r\n @if (field._deleting) {\r\n <div\r\n class=\"absolute inset-0 flex items-center justify-center bg-white/50 rounded-xl z-10\"\r\n >\r\n <div\r\n class=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\"\r\n ></div>\r\n </div>\r\n }\r\n <div class=\"flex gap-2\">\r\n <div class=\"flex-1\">\r\n <form [formGroup]=\"getFormGroup(field)\">\r\n <mt-dynamic-field\r\n [fieldName]=\"field.name\"\r\n [fieldConfig]=\"{\r\n label: field.name,\r\n readonly: true,\r\n type: getFieldType(field),\r\n }\"\r\n />\r\n </form>\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"t('edit-field')\"\r\n outlined\r\n [disabled]=\"field._deleting\"\r\n (onClick)=\"editField(field)\"\r\n ></mt-button>\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n severity=\"danger\"\r\n outlined\r\n [tooltip]=\"t('remove-field')\"\r\n [disabled]=\"field._deleting\"\r\n (onClick)=\"removeField($event, field)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n } @empty {\r\n <mt-card class=\"absolute inset-0 top-4 h-full\" paddingless>\r\n <div class=\"size-full p-4\">\r\n <div\r\n class=\"flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary\"\r\n >\r\n <mt-icon icon=\"editor.move\" class=\"text-3xl\" />\r\n <span>{{ t(\"drag-from-the-side\") }}</span>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DynamicField, selector: "mt-dynamic-field", inputs: ["fieldConfig", "fieldName"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
1609
|
+
}
|
|
1610
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBSection, decorators: [{
|
|
1611
|
+
type: Component,
|
|
1612
|
+
args: [{ selector: 'mt-fb-section', standalone: true, imports: [
|
|
1613
|
+
Button,
|
|
1614
|
+
Card,
|
|
1615
|
+
Icon,
|
|
1616
|
+
CdkDrag,
|
|
1617
|
+
CdkDropList,
|
|
1618
|
+
CdkDragPlaceholder,
|
|
1619
|
+
TranslocoDirective,
|
|
1620
|
+
DynamicField,
|
|
1621
|
+
ReactiveFormsModule,
|
|
1622
|
+
], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex flex-col\">\r\n <div\r\n class=\"flex justify-between items-center bg-primary text-primary-contrast p-2 rounded-xl\"\r\n >\r\n <mt-button\r\n size=\"small\"\r\n icon=\"arrow.chevron-down\"\r\n class=\"transition-[rotate]\"\r\n [class.rotate-180]=\"expanded()\"\r\n (onClick)=\"toggleExpanded()\"\r\n ></mt-button>\r\n <span class=\"font-bold\">\r\n {{ sectionName() }}\r\n </span>\r\n <div class=\"flex gap-2\">\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.edit-02\"\r\n [tooltip]=\"t('edit-section')\"\r\n (onClick)=\"editSection($event)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n @if (expanded()) {\r\n <div\r\n cdkDropList\r\n [id]=\"section().id\"\r\n cdkDropListOrientation=\"mixed\"\r\n [cdkDropListData]=\"fields()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n class=\"grid grid-cols-12 gap-4 relative py-4\"\r\n [class.min-h-27]=\"fields().length === 0\"\r\n >\r\n @for (field of fields(); track field.id) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"field\"\r\n [cdkDragDisabled]=\"field._pending || field._deleting\"\r\n [class]=\"\r\n getFieldColSpan(field) + ' cursor-grab active:cursor-grabbing'\r\n \"\r\n >\r\n <div\r\n *cdkDragPlaceholder\r\n [class]=\"\r\n 'h-full min-h-27 bg-black/10 rounded-2xl ' +\r\n getFieldColSpan(field)\r\n \"\r\n ></div>\r\n @if (field._pending) {\r\n <!-- Skeleton for pending field -->\r\n <mt-card class=\"h-full animate-pulse\">\r\n <div class=\"flex gap-2\">\r\n <div class=\"flex-1 space-y-3\">\r\n <div class=\"h-4 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"h-8 w-8 bg-surface-200 rounded\"></div>\r\n <div class=\"h-8 w-8 bg-surface-200 rounded\"></div>\r\n </div>\r\n </div>\r\n </mt-card>\r\n } @else {\r\n <mt-card\r\n class=\"h-full relative\"\r\n [class.opacity-50]=\"field._deleting\"\r\n >\r\n @if (field._deleting) {\r\n <div\r\n class=\"absolute inset-0 flex items-center justify-center bg-white/50 rounded-xl z-10\"\r\n >\r\n <div\r\n class=\"animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full\"\r\n ></div>\r\n </div>\r\n }\r\n <div class=\"flex gap-2\">\r\n <div class=\"flex-1\">\r\n <form [formGroup]=\"getFormGroup(field)\">\r\n <mt-dynamic-field\r\n [fieldName]=\"field.name\"\r\n [fieldConfig]=\"{\r\n label: field.name,\r\n readonly: true,\r\n type: getFieldType(field),\r\n }\"\r\n />\r\n </form>\r\n </div>\r\n <div class=\"flex flex-col gap-1\">\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.settings-01\"\r\n [tooltip]=\"t('edit-field')\"\r\n outlined\r\n [disabled]=\"field._deleting\"\r\n (onClick)=\"editField(field)\"\r\n ></mt-button>\r\n <mt-button\r\n size=\"small\"\r\n icon=\"general.trash-01\"\r\n severity=\"danger\"\r\n outlined\r\n [tooltip]=\"t('remove-field')\"\r\n [disabled]=\"field._deleting\"\r\n (onClick)=\"removeField($event, field)\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n } @empty {\r\n <mt-card class=\"absolute inset-0 top-4 h-full\" paddingless>\r\n <div class=\"size-full p-4\">\r\n <div\r\n class=\"flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary\"\r\n >\r\n <mt-icon icon=\"editor.move\" class=\"text-3xl\" />\r\n <span>{{ t(\"drag-from-the-side\") }}</span>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n" }]
|
|
1623
|
+
}], ctorParameters: () => [], propDecorators: { section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: true }] }], sectionsCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "sectionsCount", required: false }] }], allSections: [{ type: i0.Input, args: [{ isSignal: true, alias: "allSections", required: false }] }], onFieldDrop: [{ type: i0.Output, args: ["onFieldDrop"] }] } });
|
|
1624
|
+
|
|
1625
|
+
class FBPreviewForm {
|
|
1626
|
+
modalService = inject(ModalService);
|
|
1627
|
+
ref = inject(ModalRef);
|
|
1628
|
+
transloco = inject(TranslocoService);
|
|
1629
|
+
// Inputs
|
|
1630
|
+
sections = input([], ...(ngDevMode ? [{ debugName: "sections" }] : []));
|
|
1631
|
+
// Tab state
|
|
1632
|
+
activeTab = signal('create', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
|
|
1633
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1634
|
+
tabOptions = [
|
|
1635
|
+
{
|
|
1636
|
+
label: this.transloco.translate('formBuilder.create-form'),
|
|
1637
|
+
value: 'create',
|
|
1638
|
+
},
|
|
1639
|
+
{ label: this.transloco.translate('formBuilder.edit-form'), value: 'edit' },
|
|
1640
|
+
];
|
|
1641
|
+
// Form control for dynamic form
|
|
1642
|
+
formControl = new FormControl();
|
|
1643
|
+
constructor() {
|
|
1644
|
+
// Show skeleton briefly when tab changes to force rebuild
|
|
1645
|
+
effect(() => {
|
|
1646
|
+
this.activeTab(); // Track tab changes
|
|
1647
|
+
this.isLoading.set(true);
|
|
1648
|
+
setTimeout(() => this.isLoading.set(false), 50);
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
// Convert enriched sections to DynamicFormConfig based on active tab
|
|
1652
|
+
formConfig = computed(() => {
|
|
1653
|
+
const sections = this.sections();
|
|
1654
|
+
const mode = this.activeTab();
|
|
1655
|
+
return {
|
|
1656
|
+
sections: sections
|
|
1657
|
+
.map((section, sectionIndex) => {
|
|
1658
|
+
const lang = document.documentElement.lang;
|
|
1659
|
+
const sectionName = section.name[lang] ?? section.name['en'] ?? '';
|
|
1660
|
+
// Filter fields based on mode
|
|
1661
|
+
const visibleFields = section.fields.filter((field) => {
|
|
1662
|
+
if (mode === 'create') {
|
|
1663
|
+
return !field.hiddenInCreation;
|
|
1664
|
+
}
|
|
1665
|
+
else {
|
|
1666
|
+
return !field.hiddenInEditForm;
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
return {
|
|
1670
|
+
key: section.id,
|
|
1671
|
+
label: sectionName,
|
|
1672
|
+
type: 'header',
|
|
1673
|
+
columns: 12,
|
|
1674
|
+
order: section.order ?? sectionIndex,
|
|
1675
|
+
fields: visibleFields.map((field, fieldIndex) => {
|
|
1676
|
+
const colSpan = this.getColSpan(field.width);
|
|
1677
|
+
return {
|
|
1678
|
+
key: `field_${field.propertyId}`,
|
|
1679
|
+
label: field.name,
|
|
1680
|
+
type: this.mapFieldType(field.type),
|
|
1681
|
+
colSpan,
|
|
1682
|
+
order: field.order ?? fieldIndex,
|
|
1683
|
+
placeholder: field.name,
|
|
1684
|
+
};
|
|
1685
|
+
}),
|
|
1686
|
+
};
|
|
1687
|
+
})
|
|
1688
|
+
.filter((section) => section.fields.length > 0), // Hide empty sections
|
|
1689
|
+
};
|
|
1690
|
+
}, ...(ngDevMode ? [{ debugName: "formConfig" }] : []));
|
|
1691
|
+
/**
|
|
1692
|
+
* Map property view type to dynamic form field type
|
|
1693
|
+
*/
|
|
1694
|
+
mapFieldType(viewType) {
|
|
1695
|
+
const typeMap = {
|
|
1696
|
+
User: 'select',
|
|
1697
|
+
Text: 'text',
|
|
1698
|
+
LongText: 'editor-field',
|
|
1699
|
+
Percentage: 'slider',
|
|
1700
|
+
Date: 'date',
|
|
1701
|
+
Currency: 'text',
|
|
1702
|
+
Number: 'number',
|
|
1703
|
+
Lookup: 'select',
|
|
1704
|
+
LookupMultiSelect: 'select',
|
|
1705
|
+
Checkbox: 'toggle',
|
|
1706
|
+
InternalModule: 'select',
|
|
1707
|
+
DynamicList: 'select',
|
|
1708
|
+
API: 'select',
|
|
1709
|
+
Time: 'date',
|
|
1710
|
+
Status: 'select',
|
|
1711
|
+
Attachment: 'attachment',
|
|
1712
|
+
EditableListView: 'actionableTable',
|
|
1713
|
+
LookupLog: 'actionableTable',
|
|
1714
|
+
LookupMatrix: 'select',
|
|
1715
|
+
Location: 'select',
|
|
1716
|
+
};
|
|
1717
|
+
return typeMap[viewType] ?? 'text';
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Convert field width to colSpan
|
|
1721
|
+
*/
|
|
1722
|
+
getColSpan(width) {
|
|
1723
|
+
const widthMap = {
|
|
1724
|
+
'25': 3,
|
|
1725
|
+
'50': 6,
|
|
1726
|
+
'100': 12,
|
|
1727
|
+
};
|
|
1728
|
+
return widthMap[width] ?? 12;
|
|
1729
|
+
}
|
|
1730
|
+
onClose() {
|
|
1731
|
+
this.ref.close();
|
|
1732
|
+
}
|
|
1733
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBPreviewForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1734
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FBPreviewForm, isStandalone: true, selector: "mt-fb-preview-form", inputs: { sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <!-- Tabs for Create/Edit mode -->\r\n <div class=\"flex justify-center mb-4\">\r\n <mt-tabs [options]=\"tabOptions\" [(active)]=\"activeTab\"></mt-tabs>\r\n </div>\r\n\r\n <div class=\"h-full overflow-y-auto pb-10\">\r\n @if (isLoading()) {\r\n <div class=\"space-y-4 animate-pulse\">\r\n <div class=\"h-6 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded w-2/3\"></div>\r\n </div>\r\n } @else if (formConfig().sections.length > 0) {\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"formControl\"\r\n >\r\n </mt-dynamic-form>\r\n } @else {\r\n <div\r\n class=\"flex justify-center items-center h-64 text-muted-color text-lg\"\r\n >\r\n {{ t(\"no-fields-visible\") }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { 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: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "size", "fluid", "disabled"], outputs: ["activeChange", "onChange"] }] });
|
|
1735
|
+
}
|
|
1736
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FBPreviewForm, decorators: [{
|
|
1737
|
+
type: Component,
|
|
1738
|
+
args: [{ selector: 'mt-fb-preview-form', standalone: true, imports: [TranslocoDirective, ReactiveFormsModule, DynamicForm, Tabs], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div [class]=\"[modalService.contentClass, 'p-4', 'overflow-y-hidden!']\">\r\n <!-- Tabs for Create/Edit mode -->\r\n <div class=\"flex justify-center mb-4\">\r\n <mt-tabs [options]=\"tabOptions\" [(active)]=\"activeTab\"></mt-tabs>\r\n </div>\r\n\r\n <div class=\"h-full overflow-y-auto pb-10\">\r\n @if (isLoading()) {\r\n <div class=\"space-y-4 animate-pulse\">\r\n <div class=\"h-6 bg-surface-200 rounded w-1/3\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded\"></div>\r\n <div class=\"h-10 bg-surface-200 rounded w-2/3\"></div>\r\n </div>\r\n } @else if (formConfig().sections.length > 0) {\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"formControl\"\r\n >\r\n </mt-dynamic-form>\r\n } @else {\r\n <div\r\n class=\"flex justify-center items-center h-64 text-muted-color text-lg\"\r\n >\r\n {{ t(\"no-fields-visible\") }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
|
|
1739
|
+
}], ctorParameters: () => [], propDecorators: { sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }] } });
|
|
1740
|
+
|
|
1741
|
+
class FormBuilder {
|
|
1742
|
+
modalService = inject(ModalService);
|
|
1743
|
+
confirmationService = inject(ConfirmationService);
|
|
1744
|
+
translocoService = inject(TranslocoService);
|
|
1745
|
+
facade = inject(FormBuilderFacade);
|
|
1746
|
+
dialogRef;
|
|
1747
|
+
// Local UI state
|
|
1748
|
+
activeTab = signal('system', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
|
|
1749
|
+
// State from facade
|
|
1750
|
+
sections = this.facade.sections;
|
|
1751
|
+
properties = this.facade.properties;
|
|
1752
|
+
isLoading = this.facade.isLoadingFormConfiguration;
|
|
1753
|
+
error = this.facade.formConfigurationError;
|
|
1754
|
+
// Properties map for enrichment
|
|
1755
|
+
propertiesMap = computed(() => {
|
|
1756
|
+
const map = new Map();
|
|
1757
|
+
for (const prop of this.properties()) {
|
|
1758
|
+
map.set(prop.id, prop);
|
|
1759
|
+
}
|
|
1760
|
+
return map;
|
|
1761
|
+
}, ...(ngDevMode ? [{ debugName: "propertiesMap" }] : []));
|
|
1762
|
+
// Enrich sections with property data for UI display
|
|
1763
|
+
enrichedSections = computed(() => {
|
|
1764
|
+
const sections = this.sections();
|
|
1765
|
+
const propsMap = this.propertiesMap();
|
|
1766
|
+
const lang = document.documentElement.lang;
|
|
1767
|
+
return sections.map((section) => ({
|
|
1768
|
+
...section,
|
|
1769
|
+
fields: section.fields.map((field) => {
|
|
1770
|
+
const prop = propsMap.get(field.propertyId);
|
|
1771
|
+
const propName = prop?.name;
|
|
1772
|
+
const name = typeof propName === 'string'
|
|
1773
|
+
? propName
|
|
1774
|
+
: (propName?.[lang] ??
|
|
1775
|
+
propName?.['en'] ??
|
|
1776
|
+
`Property ${field.propertyId}`);
|
|
1777
|
+
return {
|
|
1778
|
+
...field,
|
|
1779
|
+
name,
|
|
1780
|
+
type: prop?.viewType || 'text',
|
|
1781
|
+
data: prop,
|
|
1782
|
+
};
|
|
1783
|
+
}),
|
|
1784
|
+
}));
|
|
1785
|
+
}, ...(ngDevMode ? [{ debugName: "enrichedSections" }] : []));
|
|
1786
|
+
// Available properties for toolbox tabs
|
|
1787
|
+
availableTabs = computed(() => {
|
|
1788
|
+
const tabs = [];
|
|
1789
|
+
const usedPropertyIds = new Set(this.sections().flatMap((s) => s.fields.map((f) => f.propertyId)));
|
|
1790
|
+
const availableProps = this.properties().filter((p) => !usedPropertyIds.has(p.id));
|
|
1791
|
+
const systemProps = availableProps.filter((p) => p['isSystem']);
|
|
1792
|
+
const customProps = availableProps.filter((p) => !p['isSystem']);
|
|
1793
|
+
if (systemProps.length > 0) {
|
|
1794
|
+
tabs.push({ id: 'system', title: 'System', properties: systemProps });
|
|
1795
|
+
}
|
|
1796
|
+
if (customProps.length > 0) {
|
|
1797
|
+
tabs.push({ id: 'custom', title: 'Custom', properties: customProps });
|
|
1798
|
+
}
|
|
1799
|
+
return tabs;
|
|
1800
|
+
}, ...(ngDevMode ? [{ debugName: "availableTabs" }] : []));
|
|
1801
|
+
constructor() {
|
|
1802
|
+
// Update active tab when tabs change
|
|
1803
|
+
effect(() => {
|
|
1804
|
+
const tabs = this.availableTabs();
|
|
1805
|
+
const currentTab = this.activeTab();
|
|
1806
|
+
if (tabs.length > 0 && !tabs.some((t) => t.id === currentTab)) {
|
|
1807
|
+
this.activeTab.set(tabs[0].id);
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
drop(event) {
|
|
1812
|
+
const targetSectionId = event.container.id;
|
|
1813
|
+
if (event.previousContainer === event.container) {
|
|
1814
|
+
// Reordering within the same section
|
|
1815
|
+
const fields = [...event.container.data];
|
|
1816
|
+
const [movedField] = fields.splice(event.previousIndex, 1);
|
|
1817
|
+
fields.splice(event.currentIndex, 0, movedField);
|
|
1818
|
+
// Build bulk reorder payload with new order values
|
|
1819
|
+
const reorderPayload = fields.map((field, index) => ({
|
|
1820
|
+
id: field.id,
|
|
1821
|
+
order: index,
|
|
1822
|
+
}));
|
|
1823
|
+
this.facade.reorderFields(targetSectionId, reorderPayload);
|
|
1824
|
+
}
|
|
1825
|
+
else if (event.previousContainer.id.startsWith('toolbox-')) {
|
|
1826
|
+
// Adding from toolbox
|
|
1827
|
+
const propertyItem = event.item.data;
|
|
1828
|
+
this.facade.addField(targetSectionId, {
|
|
1829
|
+
propertyId: propertyItem.id,
|
|
1830
|
+
width: '100',
|
|
1831
|
+
order: event.currentIndex,
|
|
1832
|
+
hiddenInCreation: false,
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
else {
|
|
1836
|
+
// Moving between sections
|
|
1837
|
+
const sourceSectionId = event.previousContainer.id;
|
|
1838
|
+
const field = event.item.data;
|
|
1839
|
+
this.facade.moveField(sourceSectionId, field.id, {
|
|
1840
|
+
targetSectionId,
|
|
1841
|
+
order: event.currentIndex,
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
addSection() {
|
|
1846
|
+
this.dialogRef = this.modalService.openModal(FBSectionForm, 'drawer', {
|
|
1847
|
+
header: this.translocoService.translate('formBuilder.add-section'),
|
|
1848
|
+
height: '20vw',
|
|
1849
|
+
styleClass: '!w-100 !absolute ',
|
|
1850
|
+
position: 'end',
|
|
1851
|
+
appendTo: '#page-content',
|
|
1852
|
+
modal: true,
|
|
1853
|
+
dismissible: true,
|
|
1854
|
+
inputValues: {
|
|
1855
|
+
sectionsCount: this.sections().length,
|
|
1856
|
+
},
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
openPreview() {
|
|
1860
|
+
this.dialogRef = this.modalService.openModal(FBPreviewForm, 'drawer', {
|
|
1861
|
+
header: this.translocoService.translate('formBuilder.preview'),
|
|
1862
|
+
styleClass: '!w-[80vw] ',
|
|
1863
|
+
position: 'end',
|
|
1864
|
+
modal: true,
|
|
1865
|
+
dismissible: true,
|
|
1866
|
+
inputValues: {
|
|
1867
|
+
sections: this.enrichedSections(),
|
|
1868
|
+
},
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
resetFormConfiguration() {
|
|
1872
|
+
this.confirmationService.confirm({
|
|
1873
|
+
type: 'dialog',
|
|
1874
|
+
acceptButtonStyleClass: 'p-button-danger',
|
|
1875
|
+
accept: () => {
|
|
1876
|
+
this.facade.resetFormConfiguration();
|
|
1877
|
+
},
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
noReturnPredicate = () => false;
|
|
1881
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1882
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: FormBuilder, isStandalone: true, selector: "mt-form-builder", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex gap-4 h-full w-full overflow-hidden\" cdkDropListGroup>\r\n <!-- Properties Sidebar -->\r\n <mt-card\r\n class=\"z-1 w-1/5 min-w-xs shrink-0 h-full flex flex-col overflow-hidden\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <div class=\"flex items-center justify-between px-4 pt-5\">\r\n <h3 class=\"text-xl font-semibold\">{{ t(\"form-elements\") }}</h3>\r\n </div>\r\n\r\n @if (properties().length === 0) {\r\n @if (isLoading()) {\r\n <!-- Properties Loading Skeleton -->\r\n <div class=\"p-4 space-y-3\">\r\n <p-skeleton height=\"2rem\" styleClass=\"mb-4\" />\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <p-skeleton height=\"3rem\" styleClass=\"rounded-lg\" />\r\n }\r\n </div>\r\n } @else {\r\n <!-- No Properties State -->\r\n <div class=\"flex-1 flex items-center justify-center p-4\">\r\n <div class=\"text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n </div>\r\n }\r\n } @else {\r\n <!-- Tabs using PrimeNG -->\r\n <p-tabs\r\n [(value)]=\"activeTab\"\r\n styleClass=\"structure-tabs\"\r\n class=\"flex flex-1 flex-col min-h-0\"\r\n >\r\n <p-tablist class=\"shrink-0\">\r\n @for (tab of availableTabs(); track tab.id) {\r\n <p-tab [value]=\"tab.id\">{{ tab.title | titlecase }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels class=\"!bg-transparent !p-0 flex-1 overflow-hidden\">\r\n @for (tab of availableTabs(); track tab.id) {\r\n <p-tabpanel [value]=\"tab.id\" class=\"h-full\">\r\n <!-- Node List -->\r\n <div\r\n class=\"space-y-1 p-4 [&_.cdk-drag-placeholder]:hidden h-full overflow-y-auto\"\r\n [id]=\"'toolbox-' + tab.id\"\r\n cdkDropList\r\n cdkDropListSortingDisabled\r\n [cdkDropListData]=\"tab.properties\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\"\r\n >\r\n @for (node of tab.properties; track $index) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"node\"\r\n class=\"group cursor-move select-none relative flex items-center gap-3 py-3 px-4 rounded-lg border border-dashed border-surface-300 hover:border-solid hover:bg-emphasis dark:border-surface-500 transition-colors\"\r\n >\r\n <div\r\n *cdkDragPlaceholder\r\n class=\"col-span-12 min-h-27 w-full rounded-2xl bg-black/10 z-1\"\r\n ></div>\r\n <span class=\"flex-1 text-base font-medium\">{{\r\n node.name\r\n }}</span>\r\n </div>\r\n }\r\n\r\n @if (tab.properties.length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">\r\n All {{ tab.title }} items are in use\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n }\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- Main Canvas Area -->\r\n <div class=\"flex flex-col gap-4 flex-1 w-full h-full overflow-y-auto\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 flex items-center gap-2\">\r\n <mt-button\r\n icon=\"layout.layout-top\"\r\n [label]=\"t('add-section')\"\r\n (onClick)=\"addSection()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n icon=\"general.eye\"\r\n [label]=\"t('preview')\"\r\n (onClick)=\"openPreview()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n icon=\"finance.credit-card-plus\"\r\n [label]=\"t('reset')\"\r\n (onClick)=\"resetFormConfiguration()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n @if (isLoading()) {\r\n <!-- Form Loading Skeleton -->\r\n @for (i of [1, 2]; track i) {\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 space-y-4\">\r\n <!-- Section header skeleton -->\r\n <div class=\"flex items-center justify-between\">\r\n <p-skeleton width=\"10rem\" height=\"1.5rem\" />\r\n <div class=\"flex gap-2\">\r\n <p-skeleton width=\"2rem\" height=\"2rem\" shape=\"circle\" />\r\n <p-skeleton width=\"2rem\" height=\"2rem\" shape=\"circle\" />\r\n </div>\r\n </div>\r\n <!-- Fields skeleton -->\r\n <div class=\"grid grid-cols-12 gap-4\">\r\n <div class=\"col-span-6\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"col-span-6\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"col-span-12\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n }\r\n } @else {\r\n @for (section of enrichedSections(); track section.id) {\r\n <mt-fb-section\r\n [section]=\"section\"\r\n [sectionsCount]=\"enrichedSections().length\"\r\n [allSections]=\"enrichedSections()\"\r\n (onFieldDrop)=\"drop($event)\"\r\n >\r\n </mt-fb-section>\r\n } @empty {\r\n <mt-card>\r\n <div class=\"h-27 p-4\">\r\n <div\r\n class=\"flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary\"\r\n >\r\n <span>{{ t(\"no-section\") }}</span>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n }\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [".cdk-drag{cursor:grab}.cdk-drag:active,.cdk-drag-preview{cursor:grabbing}.cdk-drag-placeholder{opacity:.5}.cdk-drop-list-dragging .cdk-drag{cursor:grabbing}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i1$1.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i1$1.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i1$1.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i1$1.TabList, selector: "p-tablist" }, { kind: "component", type: i1$1.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: FBSection, selector: "mt-fb-section", inputs: ["section", "sectionsCount", "allSections"], outputs: ["onFieldDrop"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i3.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "directive", type: i3.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "pipe", type: i4.TitleCasePipe, name: "titlecase" }] });
|
|
1883
|
+
}
|
|
1884
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: FormBuilder, decorators: [{
|
|
1885
|
+
type: Component,
|
|
1886
|
+
args: [{ selector: 'mt-form-builder', standalone: true, imports: [
|
|
1887
|
+
CommonModule,
|
|
1888
|
+
FormsModule,
|
|
1889
|
+
TabsModule,
|
|
1890
|
+
SkeletonModule,
|
|
1891
|
+
Button,
|
|
1892
|
+
Card,
|
|
1893
|
+
TranslocoDirective,
|
|
1894
|
+
FBSection,
|
|
1895
|
+
DragDropModule,
|
|
1896
|
+
], template: "<ng-container *transloco=\"let t; prefix: 'formBuilder'\">\r\n <div class=\"flex gap-4 h-full w-full overflow-hidden\" cdkDropListGroup>\r\n <!-- Properties Sidebar -->\r\n <mt-card\r\n class=\"z-1 w-1/5 min-w-xs shrink-0 h-full flex flex-col overflow-hidden\"\r\n >\r\n <ng-template #headless>\r\n <!-- Header -->\r\n <div class=\"flex items-center justify-between px-4 pt-5\">\r\n <h3 class=\"text-xl font-semibold\">{{ t(\"form-elements\") }}</h3>\r\n </div>\r\n\r\n @if (properties().length === 0) {\r\n @if (isLoading()) {\r\n <!-- Properties Loading Skeleton -->\r\n <div class=\"p-4 space-y-3\">\r\n <p-skeleton height=\"2rem\" styleClass=\"mb-4\" />\r\n @for (i of [1, 2, 3, 4, 5, 6]; track i) {\r\n <p-skeleton height=\"3rem\" styleClass=\"rounded-lg\" />\r\n }\r\n </div>\r\n } @else {\r\n <!-- No Properties State -->\r\n <div class=\"flex-1 flex items-center justify-center p-4\">\r\n <div class=\"text-center text-muted-color\">\r\n <p class=\"text-sm\">{{ t(\"no-properties\") }}</p>\r\n </div>\r\n </div>\r\n }\r\n } @else {\r\n <!-- Tabs using PrimeNG -->\r\n <p-tabs\r\n [(value)]=\"activeTab\"\r\n styleClass=\"structure-tabs\"\r\n class=\"flex flex-1 flex-col min-h-0\"\r\n >\r\n <p-tablist class=\"shrink-0\">\r\n @for (tab of availableTabs(); track tab.id) {\r\n <p-tab [value]=\"tab.id\">{{ tab.title | titlecase }}</p-tab>\r\n }\r\n </p-tablist>\r\n <p-tabpanels class=\"!bg-transparent !p-0 flex-1 overflow-hidden\">\r\n @for (tab of availableTabs(); track tab.id) {\r\n <p-tabpanel [value]=\"tab.id\" class=\"h-full\">\r\n <!-- Node List -->\r\n <div\r\n class=\"space-y-1 p-4 [&_.cdk-drag-placeholder]:hidden h-full overflow-y-auto\"\r\n [id]=\"'toolbox-' + tab.id\"\r\n cdkDropList\r\n cdkDropListSortingDisabled\r\n [cdkDropListData]=\"tab.properties\"\r\n [cdkDropListEnterPredicate]=\"noReturnPredicate\"\r\n >\r\n @for (node of tab.properties; track $index) {\r\n <div\r\n cdkDrag\r\n [cdkDragData]=\"node\"\r\n class=\"group cursor-move select-none relative flex items-center gap-3 py-3 px-4 rounded-lg border border-dashed border-surface-300 hover:border-solid hover:bg-emphasis dark:border-surface-500 transition-colors\"\r\n >\r\n <div\r\n *cdkDragPlaceholder\r\n class=\"col-span-12 min-h-27 w-full rounded-2xl bg-black/10 z-1\"\r\n ></div>\r\n <span class=\"flex-1 text-base font-medium\">{{\r\n node.name\r\n }}</span>\r\n </div>\r\n }\r\n\r\n @if (tab.properties.length === 0) {\r\n <div class=\"py-8 text-center text-muted-color\">\r\n <p class=\"text-sm\">\r\n All {{ tab.title }} items are in use\r\n </p>\r\n </div>\r\n }\r\n </div>\r\n </p-tabpanel>\r\n }\r\n </p-tabpanels>\r\n </p-tabs>\r\n }\r\n </ng-template>\r\n </mt-card>\r\n\r\n <!-- Main Canvas Area -->\r\n <div class=\"flex flex-col gap-4 flex-1 w-full h-full overflow-y-auto\">\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 flex items-center gap-2\">\r\n <mt-button\r\n icon=\"layout.layout-top\"\r\n [label]=\"t('add-section')\"\r\n (onClick)=\"addSection()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n icon=\"general.eye\"\r\n [label]=\"t('preview')\"\r\n (onClick)=\"openPreview()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n <mt-button\r\n icon=\"finance.credit-card-plus\"\r\n [label]=\"t('reset')\"\r\n (onClick)=\"resetFormConfiguration()\"\r\n [disabled]=\"isLoading()\"\r\n ></mt-button>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n\r\n @if (isLoading()) {\r\n <!-- Form Loading Skeleton -->\r\n @for (i of [1, 2]; track i) {\r\n <mt-card>\r\n <ng-template #headless>\r\n <div class=\"p-4 space-y-4\">\r\n <!-- Section header skeleton -->\r\n <div class=\"flex items-center justify-between\">\r\n <p-skeleton width=\"10rem\" height=\"1.5rem\" />\r\n <div class=\"flex gap-2\">\r\n <p-skeleton width=\"2rem\" height=\"2rem\" shape=\"circle\" />\r\n <p-skeleton width=\"2rem\" height=\"2rem\" shape=\"circle\" />\r\n </div>\r\n </div>\r\n <!-- Fields skeleton -->\r\n <div class=\"grid grid-cols-12 gap-4\">\r\n <div class=\"col-span-6\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"col-span-6\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n <div class=\"col-span-12\">\r\n <p-skeleton height=\"4rem\" styleClass=\"rounded-lg\" />\r\n </div>\r\n </div>\r\n </div>\r\n </ng-template>\r\n </mt-card>\r\n }\r\n } @else {\r\n @for (section of enrichedSections(); track section.id) {\r\n <mt-fb-section\r\n [section]=\"section\"\r\n [sectionsCount]=\"enrichedSections().length\"\r\n [allSections]=\"enrichedSections()\"\r\n (onFieldDrop)=\"drop($event)\"\r\n >\r\n </mt-fb-section>\r\n } @empty {\r\n <mt-card>\r\n <div class=\"h-27 p-4\">\r\n <div\r\n class=\"flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary\"\r\n >\r\n <span>{{ t(\"no-section\") }}</span>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n }\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [".cdk-drag{cursor:grab}.cdk-drag:active,.cdk-drag-preview{cursor:grabbing}.cdk-drag-placeholder{opacity:.5}.cdk-drop-list-dragging .cdk-drag{cursor:grabbing}\n"] }]
|
|
1897
|
+
}], ctorParameters: () => [] });
|
|
1898
|
+
|
|
1899
|
+
/*
|
|
1900
|
+
* Public API Surface of form-builder
|
|
1901
|
+
*/
|
|
1902
|
+
|
|
1903
|
+
/**
|
|
1904
|
+
* Generated bundle index. Do not edit.
|
|
1905
|
+
*/
|
|
1906
|
+
|
|
1907
|
+
export { AddField, AddSection, DeleteField, DeleteSection, FormBuilder, FormBuilderActionKey, FormBuilderFacade, FormBuilderState, GetFormConfiguration, MoveField, ReorderFields, ResetFormBuilderState, ResetFormConfiguration, SetModuleInfo, SetProperties, UpdateField, UpdateSection };
|
|
1908
|
+
//# sourceMappingURL=masterteam-form-builder.mjs.map
|