@masterteam/form-builder 0.0.1 → 0.0.3
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/assets/i18n/ar.json +2 -0
- package/assets/i18n/en.json +2 -0
- 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 +297 -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 -249
- package/src/lib/fb-preview-form/fb-preview-form.html +0 -31
- package/src/lib/fb-preview-form/fb-preview-form.ts +0 -142
- package/src/lib/fb-section/fb-section.html +0 -130
- package/src/lib/fb-section/fb-section.ts +0 -204
- 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 -112
- package/src/lib/form-builder.model.ts +0 -60
- 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 -98
- package/src/store/form-builder/form-builder.facade.ts +0 -194
- package/src/store/form-builder/form-builder.model.ts +0 -112
- package/src/store/form-builder/form-builder.state.ts +0 -575
- package/src/store/form-builder/index.ts +0 -5
- package/tsconfig.json +0 -31
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
computed,
|
|
4
|
-
inject,
|
|
5
|
-
input,
|
|
6
|
-
OnInit,
|
|
7
|
-
signal,
|
|
8
|
-
viewChild,
|
|
9
|
-
} from '@angular/core';
|
|
10
|
-
import { TranslocoDirective } from '@jsverse/transloco';
|
|
11
|
-
import { ReactiveFormsModule, FormControl } from '@angular/forms';
|
|
12
|
-
import { Button } from '@masterteam/components/button';
|
|
13
|
-
import { ModalService } from '@masterteam/components/modal';
|
|
14
|
-
import { ModalRef } from '@masterteam/components/dialog';
|
|
15
|
-
import {
|
|
16
|
-
FormulaEditor,
|
|
17
|
-
FormulaToolbar,
|
|
18
|
-
SmartBlock,
|
|
19
|
-
FormulaToken,
|
|
20
|
-
} from '@masterteam/components/formula';
|
|
21
|
-
import {
|
|
22
|
-
CONDITION_FUNCTION_CATEGORIES,
|
|
23
|
-
CONDITION_OPERATORS,
|
|
24
|
-
} from './condition-constants';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Field property for condition formula
|
|
28
|
-
*/
|
|
29
|
-
export interface ConditionFieldProperty {
|
|
30
|
-
/** Unique key (used in formula as @key) */
|
|
31
|
-
key: string;
|
|
32
|
-
/** Display name */
|
|
33
|
-
name: string;
|
|
34
|
-
/** Field type for hint */
|
|
35
|
-
type?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Field Conditions Dialog
|
|
40
|
-
*
|
|
41
|
-
* Allows users to define conditional display formulas for form fields.
|
|
42
|
-
* Uses FormulaToolbar + FormulaEditor directly for a visual formula editing experience.
|
|
43
|
-
*/
|
|
44
|
-
@Component({
|
|
45
|
-
selector: 'mt-fb-field-conditions',
|
|
46
|
-
standalone: true,
|
|
47
|
-
imports: [
|
|
48
|
-
TranslocoDirective,
|
|
49
|
-
ReactiveFormsModule,
|
|
50
|
-
Button,
|
|
51
|
-
FormulaToolbar,
|
|
52
|
-
FormulaEditor,
|
|
53
|
-
],
|
|
54
|
-
templateUrl: './fb-field-conditions.html',
|
|
55
|
-
})
|
|
56
|
-
export class FBFieldConditions implements OnInit {
|
|
57
|
-
protected readonly modalService = inject(ModalService);
|
|
58
|
-
private readonly ref = inject(ModalRef);
|
|
59
|
-
|
|
60
|
-
// Inputs
|
|
61
|
-
/** Initial formula (JSON string from backend) */
|
|
62
|
-
readonly initialFormula = input<string>('');
|
|
63
|
-
|
|
64
|
-
/** Available fields from the form builder (other fields that can be referenced) */
|
|
65
|
-
readonly availableFields = input<ConditionFieldProperty[]>([]);
|
|
66
|
-
|
|
67
|
-
// UI State
|
|
68
|
-
readonly submitting = signal(false);
|
|
69
|
-
|
|
70
|
-
// Form control for the formula editor (tokens array)
|
|
71
|
-
readonly formulaControl = new FormControl<FormulaToken[]>([]);
|
|
72
|
-
|
|
73
|
-
// View child for the editor
|
|
74
|
-
private readonly editorRef = viewChild<FormulaEditor>('editor');
|
|
75
|
-
|
|
76
|
-
// Condition-specific functions and operators
|
|
77
|
-
readonly functionCategories = CONDITION_FUNCTION_CATEGORIES;
|
|
78
|
-
readonly operators = CONDITION_OPERATORS;
|
|
79
|
-
|
|
80
|
-
/** Extract property keys for toolbar */
|
|
81
|
-
readonly propertyKeys = computed(() =>
|
|
82
|
-
this.availableFields().map((f) => f.key),
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
/** Toolbar labels */
|
|
86
|
-
readonly toolbarLabels = {
|
|
87
|
-
functions: 'Functions',
|
|
88
|
-
properties: 'Fields',
|
|
89
|
-
operators: 'Operators',
|
|
90
|
-
noPropertiesAvailable: 'No fields available',
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
ngOnInit(): void {
|
|
94
|
-
// Parse JSON string to tokens
|
|
95
|
-
const formula = this.initialFormula();
|
|
96
|
-
if (formula) {
|
|
97
|
-
try {
|
|
98
|
-
const tokens = JSON.parse(formula) as FormulaToken[];
|
|
99
|
-
this.formulaControl.patchValue(tokens);
|
|
100
|
-
} catch {
|
|
101
|
-
// Invalid JSON, start with empty
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Handle block insert from toolbar */
|
|
107
|
-
onBlockInsert(block: SmartBlock): void {
|
|
108
|
-
const editor = this.editorRef();
|
|
109
|
-
if (editor) {
|
|
110
|
-
editor.addBlock(block);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
onSave(): void {
|
|
115
|
-
const tokens = this.formulaControl.value ?? [];
|
|
116
|
-
const formula = tokens.length > 0 ? JSON.stringify(tokens) : '';
|
|
117
|
-
this.ref.close({ saved: true, conditionalDisplayFormula: formula });
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
onCancel(): void {
|
|
121
|
-
this.ref.close({ saved: false });
|
|
122
|
-
}
|
|
123
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
<ng-container *transloco="let t; prefix: 'formBuilder'">
|
|
2
|
-
<div [class]="[modalService.contentClass, 'p-4', 'overflow-y-hidden!']">
|
|
3
|
-
<div class="mt-4 h-full overflow-y-auto pb-10 flex flex-col gap-4">
|
|
4
|
-
<mt-dynamic-form [formConfig]="formConfig" [formControl]="formControl">
|
|
5
|
-
</mt-dynamic-form>
|
|
6
|
-
|
|
7
|
-
<!-- Show/Hide Toggle with Set Conditions -->
|
|
8
|
-
<mt-toggle-field
|
|
9
|
-
toggleShape="card"
|
|
10
|
-
[label]="t('show-hide')"
|
|
11
|
-
[descriptionCard]="t('show-hide-description')"
|
|
12
|
-
icon="general.eye"
|
|
13
|
-
[formControl]="showHideControl"
|
|
14
|
-
>
|
|
15
|
-
<ng-template #toggleCardBottom>
|
|
16
|
-
@if (showHideControl.value) {
|
|
17
|
-
<div class="mt-3">
|
|
18
|
-
<mt-button
|
|
19
|
-
[label]="t('set-conditions')"
|
|
20
|
-
size="small"
|
|
21
|
-
(onClick)="onSetConditions()"
|
|
22
|
-
></mt-button>
|
|
23
|
-
</div>
|
|
24
|
-
}
|
|
25
|
-
</ng-template>
|
|
26
|
-
</mt-toggle-field>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<div [class]="modalService.footerClass">
|
|
31
|
-
@if (initialData()) {
|
|
32
|
-
<mt-button
|
|
33
|
-
[tooltip]="t('delete')"
|
|
34
|
-
severity="danger"
|
|
35
|
-
outlined
|
|
36
|
-
icon="general.trash-01"
|
|
37
|
-
[loading]="deleting()"
|
|
38
|
-
[disabled]="submitting()"
|
|
39
|
-
(onClick)="onDelete($event)"
|
|
40
|
-
class="me-auto"
|
|
41
|
-
></mt-button>
|
|
42
|
-
}
|
|
43
|
-
<mt-button
|
|
44
|
-
[label]="t('cancel')"
|
|
45
|
-
severity="secondary"
|
|
46
|
-
[disabled]="submitting() || deleting()"
|
|
47
|
-
(onClick)="onCancel()"
|
|
48
|
-
>
|
|
49
|
-
</mt-button>
|
|
50
|
-
<mt-button
|
|
51
|
-
[disabled]="!formControl.valid || deleting()"
|
|
52
|
-
[label]="t('save')"
|
|
53
|
-
severity="primary"
|
|
54
|
-
[loading]="submitting()"
|
|
55
|
-
(onClick)="onSave()"
|
|
56
|
-
>
|
|
57
|
-
</mt-button>
|
|
58
|
-
</div>
|
|
59
|
-
</ng-container>
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
computed,
|
|
4
|
-
effect,
|
|
5
|
-
inject,
|
|
6
|
-
input,
|
|
7
|
-
signal,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
|
|
10
|
-
import { ReactiveFormsModule, FormControl } from '@angular/forms';
|
|
11
|
-
import { DynamicForm } from '@masterteam/forms/dynamic-form';
|
|
12
|
-
import {
|
|
13
|
-
DynamicFormConfig,
|
|
14
|
-
RadioCardsFieldConfig,
|
|
15
|
-
} from '@masterteam/components';
|
|
16
|
-
import { Button } from '@masterteam/components/button';
|
|
17
|
-
import { ToggleField } from '@masterteam/components/toggle-field';
|
|
18
|
-
import { ModalService } from '@masterteam/components/modal';
|
|
19
|
-
import { ModalRef } from '@masterteam/components/dialog';
|
|
20
|
-
import { ConfirmationService } from '@masterteam/components/confirmation';
|
|
21
|
-
import type {
|
|
22
|
-
EnrichedFormField,
|
|
23
|
-
EnrichedFormSection,
|
|
24
|
-
} from '../form-builder.model';
|
|
25
|
-
import { FormBuilderFacade } from '../../store/form-builder';
|
|
26
|
-
import type { FieldWidth } from '../../store/form-builder/form-builder.model';
|
|
27
|
-
import {
|
|
28
|
-
FBFieldConditions,
|
|
29
|
-
ConditionFieldProperty,
|
|
30
|
-
} from '../fb-field-conditions/fb-field-conditions';
|
|
31
|
-
|
|
32
|
-
@Component({
|
|
33
|
-
selector: 'mt-fb-field-form',
|
|
34
|
-
standalone: true,
|
|
35
|
-
imports: [
|
|
36
|
-
TranslocoDirective,
|
|
37
|
-
ReactiveFormsModule,
|
|
38
|
-
DynamicForm,
|
|
39
|
-
Button,
|
|
40
|
-
ToggleField,
|
|
41
|
-
],
|
|
42
|
-
templateUrl: './fb-field-form.html',
|
|
43
|
-
})
|
|
44
|
-
export class FBFieldForm {
|
|
45
|
-
private readonly transloco = inject(TranslocoService);
|
|
46
|
-
protected readonly modalService = inject(ModalService);
|
|
47
|
-
private readonly ref = inject(ModalRef);
|
|
48
|
-
private readonly confirmationService = inject(ConfirmationService);
|
|
49
|
-
private readonly facade = inject(FormBuilderFacade);
|
|
50
|
-
|
|
51
|
-
// Inputs
|
|
52
|
-
readonly sectionId = input<string>('');
|
|
53
|
-
readonly initialData = input<EnrichedFormField | null>(null);
|
|
54
|
-
/** All sections with enriched fields (to get available fields for conditions) */
|
|
55
|
-
readonly allSections = input<EnrichedFormSection[]>([]);
|
|
56
|
-
|
|
57
|
-
// UI State
|
|
58
|
-
readonly submitting = signal(false);
|
|
59
|
-
readonly deleting = signal(false);
|
|
60
|
-
readonly conditionalDisplayFormula = signal<string>('');
|
|
61
|
-
private conditionsDialogRef: any;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Compute available fields for condition formula
|
|
65
|
-
* Excludes the current field being edited
|
|
66
|
-
*/
|
|
67
|
-
readonly availableFields = computed<ConditionFieldProperty[]>(() => {
|
|
68
|
-
const sections = this.allSections();
|
|
69
|
-
const currentField = this.initialData();
|
|
70
|
-
const fields: ConditionFieldProperty[] = [];
|
|
71
|
-
|
|
72
|
-
for (const section of sections) {
|
|
73
|
-
for (const field of section.fields) {
|
|
74
|
-
// Exclude the current field being edited
|
|
75
|
-
if (currentField && field.id === currentField.id) continue;
|
|
76
|
-
|
|
77
|
-
fields.push({
|
|
78
|
-
key: field.name,
|
|
79
|
-
name: field.name,
|
|
80
|
-
type: field.type,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return fields;
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Form
|
|
89
|
-
readonly formControl = new FormControl();
|
|
90
|
-
readonly showHideControl = new FormControl(false);
|
|
91
|
-
readonly formConfig: DynamicFormConfig = {
|
|
92
|
-
sections: [
|
|
93
|
-
{
|
|
94
|
-
key: 'section-form',
|
|
95
|
-
type: 'none',
|
|
96
|
-
columns: 12,
|
|
97
|
-
order: 1,
|
|
98
|
-
fields: [
|
|
99
|
-
{
|
|
100
|
-
key: 'isRequired',
|
|
101
|
-
label: this.transloco.translate('formBuilder.is-required'),
|
|
102
|
-
type: 'toggle',
|
|
103
|
-
toggleShape: 'card',
|
|
104
|
-
colSpan: 12,
|
|
105
|
-
order: 1,
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
key: 'hiddenInCreation',
|
|
109
|
-
label: this.transloco.translate('formBuilder.hidden-in-creation'),
|
|
110
|
-
type: 'toggle',
|
|
111
|
-
toggleShape: 'card',
|
|
112
|
-
colSpan: 12,
|
|
113
|
-
order: 2,
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
key: 'hiddenInEditForm',
|
|
117
|
-
label: this.transloco.translate('formBuilder.hidden-in-edit-form'),
|
|
118
|
-
type: 'toggle',
|
|
119
|
-
toggleShape: 'card',
|
|
120
|
-
colSpan: 12,
|
|
121
|
-
order: 3,
|
|
122
|
-
},
|
|
123
|
-
new RadioCardsFieldConfig({
|
|
124
|
-
key: 'size',
|
|
125
|
-
label: this.transloco.translate('formBuilder.size'),
|
|
126
|
-
placeholder: this.transloco.translate('formBuilder.size'),
|
|
127
|
-
options: [
|
|
128
|
-
{ id: 's', name: 'S' },
|
|
129
|
-
{ id: 'm', name: 'M' },
|
|
130
|
-
{ id: 'l', name: 'L' },
|
|
131
|
-
],
|
|
132
|
-
order: 4,
|
|
133
|
-
size: 'small',
|
|
134
|
-
}),
|
|
135
|
-
],
|
|
136
|
-
},
|
|
137
|
-
],
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
constructor() {
|
|
141
|
-
effect(() => {
|
|
142
|
-
const data = this.initialData();
|
|
143
|
-
if (data) {
|
|
144
|
-
const widthToSize: Record<string, string> = {
|
|
145
|
-
'25': 's',
|
|
146
|
-
'50': 'm',
|
|
147
|
-
'100': 'l',
|
|
148
|
-
};
|
|
149
|
-
this.formControl.patchValue({
|
|
150
|
-
isRequired: data.isRequired ?? false,
|
|
151
|
-
hiddenInCreation: data.hiddenInCreation ?? false,
|
|
152
|
-
hiddenInEditForm: data.hiddenInEditForm ?? false,
|
|
153
|
-
size: widthToSize[data.width] ?? 'l',
|
|
154
|
-
});
|
|
155
|
-
// Set show/hide toggle based on existing value
|
|
156
|
-
this.showHideControl.patchValue(
|
|
157
|
-
data.showConditionalDisplayFormula ?? false,
|
|
158
|
-
);
|
|
159
|
-
this.conditionalDisplayFormula.set(
|
|
160
|
-
data.conditionalDisplayFormula ?? '',
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
onSave(): void {
|
|
167
|
-
if (this.formControl.invalid) return;
|
|
168
|
-
|
|
169
|
-
const formValue = this.formControl.value;
|
|
170
|
-
const field = this.initialData();
|
|
171
|
-
const sectionId = this.sectionId();
|
|
172
|
-
|
|
173
|
-
if (!field || !sectionId) return;
|
|
174
|
-
|
|
175
|
-
const widthMap: Record<string, FieldWidth> = {
|
|
176
|
-
s: '25',
|
|
177
|
-
m: '50',
|
|
178
|
-
l: '100',
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const payload = {
|
|
182
|
-
width: widthMap[formValue.size] ?? '100',
|
|
183
|
-
hiddenInCreation: formValue.hiddenInCreation ?? false,
|
|
184
|
-
hiddenInEditForm: formValue.hiddenInEditForm ?? false,
|
|
185
|
-
isRequired: formValue.isRequired ?? false,
|
|
186
|
-
showConditionalDisplayFormula: this.showHideControl.value ?? false,
|
|
187
|
-
conditionalDisplayFormula: this.showHideControl.value
|
|
188
|
-
? this.conditionalDisplayFormula()
|
|
189
|
-
: null,
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
this.submitting.set(true);
|
|
193
|
-
this.facade.updateField(sectionId, field.id, payload).subscribe({
|
|
194
|
-
next: () => this.ref.close(true),
|
|
195
|
-
error: () => this.submitting.set(false),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
onCancel(): void {
|
|
200
|
-
this.ref.close(false);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
onSetConditions(): void {
|
|
204
|
-
this.conditionsDialogRef = this.modalService.openModal(
|
|
205
|
-
FBFieldConditions,
|
|
206
|
-
'drawer',
|
|
207
|
-
{
|
|
208
|
-
header: this.transloco.translate('formBuilder.set-conditions'),
|
|
209
|
-
styleClass: '!w-[calc(100%-25rem)] !absolute ',
|
|
210
|
-
position: 'start',
|
|
211
|
-
modal: false,
|
|
212
|
-
appendTo: '#page-content',
|
|
213
|
-
inputValues: {
|
|
214
|
-
initialFormula: this.conditionalDisplayFormula(),
|
|
215
|
-
availableFields: this.availableFields(),
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
this.conditionsDialogRef.onClose.subscribe(
|
|
221
|
-
(result: { saved: boolean; conditionalDisplayFormula?: string }) => {
|
|
222
|
-
if (result?.saved) {
|
|
223
|
-
this.conditionalDisplayFormula.set(
|
|
224
|
-
result.conditionalDisplayFormula ?? '',
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
onDelete(event: Event): void {
|
|
232
|
-
const field = this.initialData();
|
|
233
|
-
const sectionId = this.sectionId();
|
|
234
|
-
|
|
235
|
-
if (!field || !sectionId) return;
|
|
236
|
-
|
|
237
|
-
this.confirmationService.confirmDelete({
|
|
238
|
-
event,
|
|
239
|
-
type: 'popup',
|
|
240
|
-
accept: () => {
|
|
241
|
-
this.deleting.set(true);
|
|
242
|
-
this.facade.deleteField(sectionId, field.id).subscribe({
|
|
243
|
-
next: () => this.ref.close(true),
|
|
244
|
-
error: () => this.deleting.set(false),
|
|
245
|
-
});
|
|
246
|
-
},
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
<ng-container *transloco="let t; prefix: 'formBuilder'">
|
|
2
|
-
<div [class]="[modalService.contentClass, 'p-4', 'overflow-y-hidden!']">
|
|
3
|
-
<!-- Tabs for Create/Edit mode -->
|
|
4
|
-
<div class="flex justify-center mb-4">
|
|
5
|
-
<mt-tabs [options]="tabOptions" [(active)]="activeTab"></mt-tabs>
|
|
6
|
-
</div>
|
|
7
|
-
|
|
8
|
-
<div class="h-full overflow-y-auto pb-10">
|
|
9
|
-
@if (isLoading()) {
|
|
10
|
-
<div class="space-y-4 animate-pulse">
|
|
11
|
-
<div class="h-6 bg-surface-200 rounded w-1/3"></div>
|
|
12
|
-
<div class="h-10 bg-surface-200 rounded"></div>
|
|
13
|
-
<div class="h-10 bg-surface-200 rounded"></div>
|
|
14
|
-
<div class="h-10 bg-surface-200 rounded w-2/3"></div>
|
|
15
|
-
</div>
|
|
16
|
-
} @else if (formConfig().sections.length > 0) {
|
|
17
|
-
<mt-dynamic-form
|
|
18
|
-
[formConfig]="formConfig()"
|
|
19
|
-
[formControl]="formControl"
|
|
20
|
-
>
|
|
21
|
-
</mt-dynamic-form>
|
|
22
|
-
} @else {
|
|
23
|
-
<div
|
|
24
|
-
class="flex justify-center items-center h-64 text-muted-color text-lg"
|
|
25
|
-
>
|
|
26
|
-
{{ t("no-fields-visible") }}
|
|
27
|
-
</div>
|
|
28
|
-
}
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</ng-container>
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
computed,
|
|
4
|
-
effect,
|
|
5
|
-
inject,
|
|
6
|
-
input,
|
|
7
|
-
signal,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
|
|
10
|
-
import { ReactiveFormsModule, FormControl } from '@angular/forms';
|
|
11
|
-
import { DynamicForm } from '@masterteam/forms/dynamic-form';
|
|
12
|
-
import { DynamicFormConfig, DynamicFieldConfig } from '@masterteam/components';
|
|
13
|
-
import { Tabs } from '@masterteam/components/tabs';
|
|
14
|
-
import { ModalService } from '@masterteam/components/modal';
|
|
15
|
-
import { ModalRef } from '@masterteam/components/dialog';
|
|
16
|
-
import type { EnrichedFormSection } from '../form-builder.model';
|
|
17
|
-
|
|
18
|
-
type PreviewMode = 'create' | 'edit';
|
|
19
|
-
|
|
20
|
-
@Component({
|
|
21
|
-
selector: 'mt-fb-preview-form',
|
|
22
|
-
standalone: true,
|
|
23
|
-
imports: [TranslocoDirective, ReactiveFormsModule, DynamicForm, Tabs],
|
|
24
|
-
templateUrl: './fb-preview-form.html',
|
|
25
|
-
})
|
|
26
|
-
export class FBPreviewForm {
|
|
27
|
-
protected readonly modalService = inject(ModalService);
|
|
28
|
-
private readonly ref = inject(ModalRef);
|
|
29
|
-
private readonly transloco = inject(TranslocoService);
|
|
30
|
-
|
|
31
|
-
// Inputs
|
|
32
|
-
readonly sections = input<EnrichedFormSection[]>([]);
|
|
33
|
-
|
|
34
|
-
// Tab state
|
|
35
|
-
readonly activeTab = signal<PreviewMode>('create');
|
|
36
|
-
readonly isLoading = signal(false);
|
|
37
|
-
readonly tabOptions = [
|
|
38
|
-
{
|
|
39
|
-
label: this.transloco.translate('formBuilder.create-form'),
|
|
40
|
-
value: 'create',
|
|
41
|
-
},
|
|
42
|
-
{ label: this.transloco.translate('formBuilder.edit-form'), value: 'edit' },
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
// Form control for dynamic form
|
|
46
|
-
readonly formControl = new FormControl();
|
|
47
|
-
|
|
48
|
-
constructor() {
|
|
49
|
-
// Show skeleton briefly when tab changes to force rebuild
|
|
50
|
-
effect(() => {
|
|
51
|
-
this.activeTab(); // Track tab changes
|
|
52
|
-
this.isLoading.set(true);
|
|
53
|
-
setTimeout(() => this.isLoading.set(false), 50);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Convert enriched sections to DynamicFormConfig based on active tab
|
|
58
|
-
readonly formConfig = computed<DynamicFormConfig>(() => {
|
|
59
|
-
const sections = this.sections();
|
|
60
|
-
const mode = this.activeTab();
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
sections: sections
|
|
64
|
-
.map((section, sectionIndex) => {
|
|
65
|
-
const lang = document.documentElement.lang as 'en' | 'ar';
|
|
66
|
-
const sectionName = section.name[lang] ?? section.name['en'] ?? '';
|
|
67
|
-
|
|
68
|
-
// Filter fields based on mode
|
|
69
|
-
const visibleFields = section.fields.filter((field) => {
|
|
70
|
-
if (mode === 'create') {
|
|
71
|
-
return !field.hiddenInCreation;
|
|
72
|
-
} else {
|
|
73
|
-
return !field.hiddenInEditForm;
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
key: section.id,
|
|
79
|
-
label: sectionName,
|
|
80
|
-
type: 'header' as const,
|
|
81
|
-
columns: 12,
|
|
82
|
-
order: section.order ?? sectionIndex,
|
|
83
|
-
fields: visibleFields.map((field, fieldIndex) => {
|
|
84
|
-
const colSpan = this.getColSpan(field.width);
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
key: `field_${field.propertyId}`,
|
|
88
|
-
label: field.name,
|
|
89
|
-
type: this.mapFieldType(field.type),
|
|
90
|
-
colSpan,
|
|
91
|
-
order: field.order ?? fieldIndex,
|
|
92
|
-
placeholder: field.name,
|
|
93
|
-
} as DynamicFieldConfig;
|
|
94
|
-
}),
|
|
95
|
-
};
|
|
96
|
-
})
|
|
97
|
-
.filter((section) => section.fields.length > 0), // Hide empty sections
|
|
98
|
-
};
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Map property view type to dynamic form field type
|
|
103
|
-
*/
|
|
104
|
-
private mapFieldType(viewType: string): string {
|
|
105
|
-
const typeMap: Record<string, string> = {
|
|
106
|
-
text: 'text',
|
|
107
|
-
number: 'number',
|
|
108
|
-
date: 'date',
|
|
109
|
-
select: 'select',
|
|
110
|
-
multiselect: 'multiselect',
|
|
111
|
-
toggle: 'toggle',
|
|
112
|
-
checkbox: 'checkbox',
|
|
113
|
-
textarea: 'textarea',
|
|
114
|
-
editor: 'editor',
|
|
115
|
-
color: 'color',
|
|
116
|
-
slider: 'slider',
|
|
117
|
-
radio: 'radio',
|
|
118
|
-
'radio-cards': 'radio-cards',
|
|
119
|
-
upload: 'upload',
|
|
120
|
-
'user-search': 'user-search',
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
return typeMap[viewType?.toLowerCase()] ?? 'text';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Convert field width to colSpan
|
|
128
|
-
*/
|
|
129
|
-
private getColSpan(width: string): number {
|
|
130
|
-
const widthMap: Record<string, number> = {
|
|
131
|
-
'25': 3,
|
|
132
|
-
'50': 6,
|
|
133
|
-
'100': 12,
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
return widthMap[width] ?? 12;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
onClose(): void {
|
|
140
|
-
this.ref.close();
|
|
141
|
-
}
|
|
142
|
-
}
|