@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
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
<ng-container *transloco="let t; prefix: 'formBuilder'">
|
|
2
|
-
<div class="flex flex-col">
|
|
3
|
-
<div
|
|
4
|
-
class="flex justify-between items-center bg-primary text-primary-contrast p-2 rounded-xl"
|
|
5
|
-
>
|
|
6
|
-
<mt-button
|
|
7
|
-
size="small"
|
|
8
|
-
icon="arrow.chevron-down"
|
|
9
|
-
class="transition-[rotate]"
|
|
10
|
-
[class.rotate-180]="expanded()"
|
|
11
|
-
(onClick)="toggleExpanded()"
|
|
12
|
-
></mt-button>
|
|
13
|
-
<span class="font-bold">
|
|
14
|
-
{{ sectionName() }}
|
|
15
|
-
</span>
|
|
16
|
-
<div class="flex gap-2">
|
|
17
|
-
<mt-button
|
|
18
|
-
size="small"
|
|
19
|
-
icon="general.edit-02"
|
|
20
|
-
[tooltip]="t('edit-section')"
|
|
21
|
-
(onClick)="editSection($event)"
|
|
22
|
-
></mt-button>
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
@if (expanded()) {
|
|
26
|
-
<div
|
|
27
|
-
cdkDropList
|
|
28
|
-
[id]="section().id"
|
|
29
|
-
cdkDropListOrientation="mixed"
|
|
30
|
-
[cdkDropListData]="fields()"
|
|
31
|
-
(cdkDropListDropped)="onDrop($event)"
|
|
32
|
-
class="grid grid-cols-12 gap-4 relative py-4"
|
|
33
|
-
[class.min-h-27]="fields().length === 0"
|
|
34
|
-
>
|
|
35
|
-
@for (field of fields(); track field.id) {
|
|
36
|
-
<div
|
|
37
|
-
cdkDrag
|
|
38
|
-
[cdkDragData]="field"
|
|
39
|
-
[cdkDragDisabled]="field._pending || field._deleting"
|
|
40
|
-
[class]="
|
|
41
|
-
getFieldColSpan(field) + ' cursor-grab active:cursor-grabbing'
|
|
42
|
-
"
|
|
43
|
-
>
|
|
44
|
-
<div
|
|
45
|
-
*cdkDragPlaceholder
|
|
46
|
-
[class]="
|
|
47
|
-
'h-full min-h-27 bg-black/10 rounded-2xl ' +
|
|
48
|
-
getFieldColSpan(field)
|
|
49
|
-
"
|
|
50
|
-
></div>
|
|
51
|
-
@if (field._pending) {
|
|
52
|
-
<!-- Skeleton for pending field -->
|
|
53
|
-
<mt-card class="h-full animate-pulse">
|
|
54
|
-
<div class="flex gap-2">
|
|
55
|
-
<div class="flex-1 space-y-3">
|
|
56
|
-
<div class="h-4 bg-surface-200 rounded w-1/3"></div>
|
|
57
|
-
<div class="h-10 bg-surface-200 rounded"></div>
|
|
58
|
-
</div>
|
|
59
|
-
<div class="flex flex-col gap-1">
|
|
60
|
-
<div class="h-8 w-8 bg-surface-200 rounded"></div>
|
|
61
|
-
<div class="h-8 w-8 bg-surface-200 rounded"></div>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</mt-card>
|
|
65
|
-
} @else {
|
|
66
|
-
<mt-card
|
|
67
|
-
class="h-full relative"
|
|
68
|
-
[class.opacity-50]="field._deleting"
|
|
69
|
-
>
|
|
70
|
-
@if (field._deleting) {
|
|
71
|
-
<div
|
|
72
|
-
class="absolute inset-0 flex items-center justify-center bg-white/50 rounded-xl z-10"
|
|
73
|
-
>
|
|
74
|
-
<div
|
|
75
|
-
class="animate-spin h-6 w-6 border-2 border-primary border-t-transparent rounded-full"
|
|
76
|
-
></div>
|
|
77
|
-
</div>
|
|
78
|
-
}
|
|
79
|
-
<div class="flex gap-2">
|
|
80
|
-
<div class="flex-1">
|
|
81
|
-
<form [formGroup]="getFormGroup(field)">
|
|
82
|
-
<mt-dynamic-field
|
|
83
|
-
[fieldName]="field.name"
|
|
84
|
-
[fieldConfig]="{
|
|
85
|
-
label: field.name,
|
|
86
|
-
readonly: true,
|
|
87
|
-
type: getFieldType(field),
|
|
88
|
-
}"
|
|
89
|
-
/>
|
|
90
|
-
</form>
|
|
91
|
-
</div>
|
|
92
|
-
<div class="flex flex-col gap-1">
|
|
93
|
-
<mt-button
|
|
94
|
-
size="small"
|
|
95
|
-
icon="general.settings-01"
|
|
96
|
-
[tooltip]="t('edit-field')"
|
|
97
|
-
outlined
|
|
98
|
-
[disabled]="field._deleting"
|
|
99
|
-
(onClick)="editField(field)"
|
|
100
|
-
></mt-button>
|
|
101
|
-
<mt-button
|
|
102
|
-
size="small"
|
|
103
|
-
icon="general.trash-01"
|
|
104
|
-
severity="danger"
|
|
105
|
-
outlined
|
|
106
|
-
[tooltip]="t('remove-field')"
|
|
107
|
-
[disabled]="field._deleting"
|
|
108
|
-
(onClick)="removeField($event, field)"
|
|
109
|
-
></mt-button>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
</mt-card>
|
|
113
|
-
}
|
|
114
|
-
</div>
|
|
115
|
-
} @empty {
|
|
116
|
-
<mt-card class="absolute inset-0 top-4 h-full" paddingless>
|
|
117
|
-
<div class="size-full p-4">
|
|
118
|
-
<div
|
|
119
|
-
class="flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary"
|
|
120
|
-
>
|
|
121
|
-
<mt-icon icon="editor.move" class="text-3xl" />
|
|
122
|
-
<span>{{ t("drag-from-the-side") }}</span>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
</mt-card>
|
|
126
|
-
}
|
|
127
|
-
</div>
|
|
128
|
-
}
|
|
129
|
-
</div>
|
|
130
|
-
</ng-container>
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
computed,
|
|
4
|
-
inject,
|
|
5
|
-
input,
|
|
6
|
-
output,
|
|
7
|
-
signal,
|
|
8
|
-
effect,
|
|
9
|
-
} from '@angular/core';
|
|
10
|
-
import type {
|
|
11
|
-
EnrichedFormField,
|
|
12
|
-
EnrichedFormSection,
|
|
13
|
-
} from '../../store/form-builder/form-builder.model';
|
|
14
|
-
import { ConfirmationService } from '@masterteam/components/confirmation';
|
|
15
|
-
import { ModalService } from '@masterteam/components/modal';
|
|
16
|
-
import { TranslocoService } from '@jsverse/transloco';
|
|
17
|
-
import { Button } from '@masterteam/components/button';
|
|
18
|
-
import { Card } from '@masterteam/components/card';
|
|
19
|
-
import {
|
|
20
|
-
CdkDrag,
|
|
21
|
-
CdkDragDrop,
|
|
22
|
-
CdkDragPlaceholder,
|
|
23
|
-
CdkDropList,
|
|
24
|
-
} from '@angular/cdk/drag-drop';
|
|
25
|
-
import { TranslocoDirective } from '@jsverse/transloco';
|
|
26
|
-
import { DynamicField } from '@masterteam/forms/dynamic-field';
|
|
27
|
-
import { ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms';
|
|
28
|
-
import { Icon } from '@masterteam/icons';
|
|
29
|
-
import { FormBuilderFacade } from '../../store/form-builder';
|
|
30
|
-
import { FBFieldForm } from '../fb-field-form/fb-field-form';
|
|
31
|
-
import { FBSectionForm } from '../fb-section-form/fb-section-form';
|
|
32
|
-
|
|
33
|
-
@Component({
|
|
34
|
-
selector: 'mt-fb-section',
|
|
35
|
-
standalone: true,
|
|
36
|
-
imports: [
|
|
37
|
-
Button,
|
|
38
|
-
Card,
|
|
39
|
-
Icon,
|
|
40
|
-
CdkDrag,
|
|
41
|
-
CdkDropList,
|
|
42
|
-
CdkDragPlaceholder,
|
|
43
|
-
TranslocoDirective,
|
|
44
|
-
DynamicField,
|
|
45
|
-
ReactiveFormsModule,
|
|
46
|
-
],
|
|
47
|
-
templateUrl: './fb-section.html',
|
|
48
|
-
})
|
|
49
|
-
export class FBSection {
|
|
50
|
-
private readonly confirmationService = inject(ConfirmationService);
|
|
51
|
-
private readonly modalService = inject(ModalService);
|
|
52
|
-
private readonly translocoService = inject(TranslocoService);
|
|
53
|
-
private readonly facade = inject(FormBuilderFacade);
|
|
54
|
-
|
|
55
|
-
// Inputs
|
|
56
|
-
readonly section = input.required<EnrichedFormSection>();
|
|
57
|
-
readonly sectionsCount = input<number>(0);
|
|
58
|
-
/** All sections - used to get available fields for condition formulas */
|
|
59
|
-
readonly allSections = input<EnrichedFormSection[]>([]);
|
|
60
|
-
|
|
61
|
-
// Outputs - only keep drag/drop since it needs coordination with parent drop list group
|
|
62
|
-
readonly onFieldDrop = output<CdkDragDrop<EnrichedFormField[]>>();
|
|
63
|
-
|
|
64
|
-
// Computed
|
|
65
|
-
readonly sectionName = computed(() => {
|
|
66
|
-
const lang = document.documentElement.lang as 'en' | 'ar';
|
|
67
|
-
const section = this.section();
|
|
68
|
-
return section.name[lang] ?? section.name['en'];
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
readonly fields = computed(() => this.section().fields);
|
|
72
|
-
|
|
73
|
-
// UI State
|
|
74
|
-
readonly expanded = signal(true);
|
|
75
|
-
|
|
76
|
-
// Form groups cache for dynamic fields
|
|
77
|
-
private readonly formGroupsCache = new Map<string, FormGroup>();
|
|
78
|
-
|
|
79
|
-
constructor() {
|
|
80
|
-
// Cleanup stale form groups when fields change
|
|
81
|
-
effect(() => {
|
|
82
|
-
const currentIds = new Set(this.fields().map((f) => f.id));
|
|
83
|
-
for (const id of this.formGroupsCache.keys()) {
|
|
84
|
-
if (!currentIds.has(id)) {
|
|
85
|
-
this.formGroupsCache.delete(id);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
getFormGroup(field: EnrichedFormField): FormGroup {
|
|
92
|
-
let fg = this.formGroupsCache.get(field.id);
|
|
93
|
-
|
|
94
|
-
if (fg) {
|
|
95
|
-
// Check if field name changed
|
|
96
|
-
if (!fg.contains(field.name)) {
|
|
97
|
-
const oldKey = Object.keys(fg.controls)[0];
|
|
98
|
-
const value = oldKey ? fg.get(oldKey)?.value : field.data?.data;
|
|
99
|
-
|
|
100
|
-
fg = new FormGroup({
|
|
101
|
-
[field.name]: new FormControl(value),
|
|
102
|
-
});
|
|
103
|
-
this.formGroupsCache.set(field.id, fg);
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
fg = new FormGroup({
|
|
107
|
-
[field.name]: new FormControl(field.data?.data),
|
|
108
|
-
});
|
|
109
|
-
this.formGroupsCache.set(field.id, fg);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return fg;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
toggleExpanded(): void {
|
|
116
|
-
this.expanded.update((v) => !v);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
onDrop(event: CdkDragDrop<EnrichedFormField[]>): void {
|
|
120
|
-
this.onFieldDrop.emit(event);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
editSection(event: Event): void {
|
|
124
|
-
event.stopPropagation();
|
|
125
|
-
const section = this.section();
|
|
126
|
-
|
|
127
|
-
this.modalService.openModal(FBSectionForm, 'drawer', {
|
|
128
|
-
header: this.translocoService.translate('formBuilder.edit-section'),
|
|
129
|
-
height: '20vw',
|
|
130
|
-
styleClass: '!w-100 !absolute ',
|
|
131
|
-
position: 'end',
|
|
132
|
-
appendTo: '#page-content',
|
|
133
|
-
modal: true,
|
|
134
|
-
dismissible: true,
|
|
135
|
-
|
|
136
|
-
inputValues: {
|
|
137
|
-
sectionId: section.id,
|
|
138
|
-
initialData: section.name,
|
|
139
|
-
sectionsCount: this.sectionsCount(),
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
removeField(event: Event, field: EnrichedFormField): void {
|
|
145
|
-
this.confirmationService.confirmDelete({
|
|
146
|
-
event,
|
|
147
|
-
type: 'popup',
|
|
148
|
-
accept: () => {
|
|
149
|
-
this.facade.deleteField(this.section().id, field.id);
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
editField(field: EnrichedFormField): void {
|
|
155
|
-
this.modalService.openModal(FBFieldForm, 'drawer', {
|
|
156
|
-
header: this.translocoService.translate('formBuilder.field-settings'),
|
|
157
|
-
height: '20vw',
|
|
158
|
-
styleClass: '!w-100 !absolute !shadow-none',
|
|
159
|
-
position: 'end',
|
|
160
|
-
modal: true,
|
|
161
|
-
dismissible: true,
|
|
162
|
-
|
|
163
|
-
appendTo: '#page-content',
|
|
164
|
-
inputValues: {
|
|
165
|
-
initialData: field,
|
|
166
|
-
sectionId: this.section().id,
|
|
167
|
-
allSections: this.allSections(),
|
|
168
|
-
},
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
getFieldColSpan(field: EnrichedFormField): string {
|
|
173
|
-
switch (field.width) {
|
|
174
|
-
case '100':
|
|
175
|
-
return 'col-span-12';
|
|
176
|
-
case '50':
|
|
177
|
-
return 'col-span-6';
|
|
178
|
-
case '25':
|
|
179
|
-
return 'col-span-3';
|
|
180
|
-
default:
|
|
181
|
-
return 'col-span-12';
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
getFieldType(field: EnrichedFormField): string {
|
|
186
|
-
const typeMap: Record<string, string> = {
|
|
187
|
-
User: 'select',
|
|
188
|
-
Text: 'text',
|
|
189
|
-
LongText: 'editor-field',
|
|
190
|
-
Percentage: 'slider',
|
|
191
|
-
Date: 'date',
|
|
192
|
-
Currency: 'text',
|
|
193
|
-
Number: 'number',
|
|
194
|
-
Lookup: 'select',
|
|
195
|
-
LookupMultiSelect: 'select',
|
|
196
|
-
Checkbox: 'toggle',
|
|
197
|
-
InternalModule: 'select',
|
|
198
|
-
DynamicList: 'select',
|
|
199
|
-
API: 'select',
|
|
200
|
-
Time: 'date',
|
|
201
|
-
Status: 'select',
|
|
202
|
-
Attachment: 'attachment',
|
|
203
|
-
EditableListView: 'actionableTable',
|
|
204
|
-
LookupLog: 'actionableTable',
|
|
205
|
-
LookupMatrix: 'select',
|
|
206
|
-
Location: 'select',
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
return typeMap[field.type] ?? 'text';
|
|
210
|
-
}
|
|
211
|
-
}
|
|
@@ -1,38 +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">
|
|
4
|
-
<mt-dynamic-form [formConfig]="formConfig" [formControl]="formControl">
|
|
5
|
-
</mt-dynamic-form>
|
|
6
|
-
</div>
|
|
7
|
-
</div>
|
|
8
|
-
|
|
9
|
-
<div [class]="modalService.footerClass">
|
|
10
|
-
@if (sectionId()) {
|
|
11
|
-
<mt-button
|
|
12
|
-
[tooltip]="t('delete')"
|
|
13
|
-
severity="danger"
|
|
14
|
-
outlined
|
|
15
|
-
icon="general.trash-01"
|
|
16
|
-
[loading]="deleting()"
|
|
17
|
-
[disabled]="submitting()"
|
|
18
|
-
(onClick)="onDelete($event)"
|
|
19
|
-
class="me-auto"
|
|
20
|
-
></mt-button>
|
|
21
|
-
}
|
|
22
|
-
<mt-button
|
|
23
|
-
[label]="t('cancel')"
|
|
24
|
-
severity="secondary"
|
|
25
|
-
[disabled]="submitting() || deleting()"
|
|
26
|
-
(onClick)="onCancel()"
|
|
27
|
-
>
|
|
28
|
-
</mt-button>
|
|
29
|
-
<mt-button
|
|
30
|
-
[disabled]="!formControl.valid || deleting()"
|
|
31
|
-
[label]="t('save')"
|
|
32
|
-
severity="primary"
|
|
33
|
-
[loading]="submitting()"
|
|
34
|
-
(onClick)="onSave()"
|
|
35
|
-
>
|
|
36
|
-
</mt-button>
|
|
37
|
-
</div>
|
|
38
|
-
</ng-container>
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { Component, effect, inject, input, signal } from '@angular/core';
|
|
2
|
-
import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
|
|
3
|
-
import { ReactiveFormsModule, FormControl } from '@angular/forms';
|
|
4
|
-
import { DynamicForm } from '@masterteam/forms/dynamic-form';
|
|
5
|
-
import { DynamicFormConfig } from '@masterteam/components';
|
|
6
|
-
import { Button } from '@masterteam/components/button';
|
|
7
|
-
import { ModalService } from '@masterteam/components/modal';
|
|
8
|
-
import { ModalRef } from '@masterteam/components/dialog';
|
|
9
|
-
import { ConfirmationService } from '@masterteam/components/confirmation';
|
|
10
|
-
import { FormBuilderFacade } from '../../store/form-builder';
|
|
11
|
-
|
|
12
|
-
@Component({
|
|
13
|
-
selector: 'mt-fb-section-form',
|
|
14
|
-
standalone: true,
|
|
15
|
-
imports: [TranslocoDirective, ReactiveFormsModule, DynamicForm, Button],
|
|
16
|
-
templateUrl: './fb-section-form.html',
|
|
17
|
-
})
|
|
18
|
-
export class FBSectionForm {
|
|
19
|
-
private readonly transloco = inject(TranslocoService);
|
|
20
|
-
protected readonly modalService = inject(ModalService);
|
|
21
|
-
private readonly ref = inject(ModalRef);
|
|
22
|
-
private readonly confirmationService = inject(ConfirmationService);
|
|
23
|
-
private readonly facade = inject(FormBuilderFacade);
|
|
24
|
-
|
|
25
|
-
// Inputs
|
|
26
|
-
readonly sectionId = input<string | null>(null);
|
|
27
|
-
readonly initialData = input<{ ar: string; en: string } | null>(null);
|
|
28
|
-
readonly sectionsCount = input<number>(0);
|
|
29
|
-
|
|
30
|
-
// UI State
|
|
31
|
-
readonly submitting = signal(false);
|
|
32
|
-
readonly deleting = signal(false);
|
|
33
|
-
|
|
34
|
-
// Form
|
|
35
|
-
readonly formControl = new FormControl();
|
|
36
|
-
readonly formConfig: DynamicFormConfig = {
|
|
37
|
-
sections: [
|
|
38
|
-
{
|
|
39
|
-
key: 'section-form',
|
|
40
|
-
type: 'none',
|
|
41
|
-
columns: 12,
|
|
42
|
-
order: 1,
|
|
43
|
-
fields: [
|
|
44
|
-
{
|
|
45
|
-
key: 'name-ar',
|
|
46
|
-
label: this.transloco.translate('formBuilder.name-ar'),
|
|
47
|
-
type: 'text',
|
|
48
|
-
placeholder: this.transloco.translate('formBuilder.name-ar'),
|
|
49
|
-
colSpan: 12,
|
|
50
|
-
order: 1,
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
key: 'name-en',
|
|
54
|
-
label: this.transloco.translate('formBuilder.name-en'),
|
|
55
|
-
type: 'text',
|
|
56
|
-
placeholder: this.transloco.translate('formBuilder.name-en'),
|
|
57
|
-
colSpan: 12,
|
|
58
|
-
order: 2,
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
constructor() {
|
|
66
|
-
effect(() => {
|
|
67
|
-
const data = this.initialData();
|
|
68
|
-
if (data) {
|
|
69
|
-
this.formControl.patchValue({
|
|
70
|
-
'name-ar': data.ar,
|
|
71
|
-
'name-en': data.en,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
onSave(): void {
|
|
78
|
-
if (this.formControl.invalid) return;
|
|
79
|
-
|
|
80
|
-
const formValue = this.formControl.value;
|
|
81
|
-
const payload = {
|
|
82
|
-
name: {
|
|
83
|
-
ar: formValue['name-ar'],
|
|
84
|
-
en: formValue['name-en'],
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
this.submitting.set(true);
|
|
89
|
-
const sectionId = this.sectionId();
|
|
90
|
-
|
|
91
|
-
if (sectionId) {
|
|
92
|
-
// Update existing section
|
|
93
|
-
this.facade.updateSection(sectionId, payload).subscribe({
|
|
94
|
-
next: () => this.ref.close(true),
|
|
95
|
-
error: () => this.submitting.set(false),
|
|
96
|
-
});
|
|
97
|
-
} else {
|
|
98
|
-
// Create new section
|
|
99
|
-
this.facade
|
|
100
|
-
.addSection({ ...payload, order: this.sectionsCount() })
|
|
101
|
-
.subscribe({
|
|
102
|
-
next: () => this.ref.close(true),
|
|
103
|
-
error: () => this.submitting.set(false),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
onCancel(): void {
|
|
109
|
-
this.ref.close(false);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
onDelete(event: Event): void {
|
|
113
|
-
const sectionId = this.sectionId();
|
|
114
|
-
if (!sectionId) return;
|
|
115
|
-
|
|
116
|
-
this.confirmationService.confirmDelete({
|
|
117
|
-
event,
|
|
118
|
-
type: 'popup',
|
|
119
|
-
accept: () => {
|
|
120
|
-
this.deleting.set(true);
|
|
121
|
-
this.facade.deleteSection(sectionId).subscribe({
|
|
122
|
-
next: () => this.ref.close(true),
|
|
123
|
-
error: () => this.deleting.set(false),
|
|
124
|
-
});
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
<ng-container *transloco="let t; prefix: 'formBuilder'">
|
|
2
|
-
<div class="flex gap-4 h-full w-full overflow-hidden" cdkDropListGroup>
|
|
3
|
-
<!-- Properties Sidebar -->
|
|
4
|
-
<mt-card
|
|
5
|
-
class="z-1 w-1/5 min-w-xs shrink-0 h-full flex flex-col overflow-hidden"
|
|
6
|
-
>
|
|
7
|
-
<ng-template #headless>
|
|
8
|
-
<!-- Header -->
|
|
9
|
-
<div class="flex items-center justify-between px-4 pt-5">
|
|
10
|
-
<h3 class="text-xl font-semibold">{{ t("form-elements") }}</h3>
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
@if (properties().length === 0) {
|
|
14
|
-
@if (isLoading()) {
|
|
15
|
-
<!-- Properties Loading Skeleton -->
|
|
16
|
-
<div class="p-4 space-y-3">
|
|
17
|
-
<p-skeleton height="2rem" styleClass="mb-4" />
|
|
18
|
-
@for (i of [1, 2, 3, 4, 5, 6]; track i) {
|
|
19
|
-
<p-skeleton height="3rem" styleClass="rounded-lg" />
|
|
20
|
-
}
|
|
21
|
-
</div>
|
|
22
|
-
} @else {
|
|
23
|
-
<!-- No Properties State -->
|
|
24
|
-
<div class="flex-1 flex items-center justify-center p-4">
|
|
25
|
-
<div class="text-center text-muted-color">
|
|
26
|
-
<p class="text-sm">{{ t("no-properties") }}</p>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
}
|
|
30
|
-
} @else {
|
|
31
|
-
<!-- Tabs using PrimeNG -->
|
|
32
|
-
<p-tabs
|
|
33
|
-
[(value)]="activeTab"
|
|
34
|
-
styleClass="structure-tabs"
|
|
35
|
-
class="flex flex-1 flex-col min-h-0"
|
|
36
|
-
>
|
|
37
|
-
<p-tablist class="shrink-0">
|
|
38
|
-
@for (tab of availableTabs(); track tab.id) {
|
|
39
|
-
<p-tab [value]="tab.id">{{ tab.title | titlecase }}</p-tab>
|
|
40
|
-
}
|
|
41
|
-
</p-tablist>
|
|
42
|
-
<p-tabpanels class="!bg-transparent !p-0 flex-1 overflow-hidden">
|
|
43
|
-
@for (tab of availableTabs(); track tab.id) {
|
|
44
|
-
<p-tabpanel [value]="tab.id" class="h-full">
|
|
45
|
-
<!-- Node List -->
|
|
46
|
-
<div
|
|
47
|
-
class="space-y-1 p-4 [&_.cdk-drag-placeholder]:hidden h-full overflow-y-auto"
|
|
48
|
-
[id]="'toolbox-' + tab.id"
|
|
49
|
-
cdkDropList
|
|
50
|
-
cdkDropListSortingDisabled
|
|
51
|
-
[cdkDropListData]="tab.properties"
|
|
52
|
-
[cdkDropListEnterPredicate]="noReturnPredicate"
|
|
53
|
-
>
|
|
54
|
-
@for (node of tab.properties; track $index) {
|
|
55
|
-
<div
|
|
56
|
-
cdkDrag
|
|
57
|
-
[cdkDragData]="node"
|
|
58
|
-
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"
|
|
59
|
-
>
|
|
60
|
-
<div
|
|
61
|
-
*cdkDragPlaceholder
|
|
62
|
-
class="col-span-12 min-h-27 w-full rounded-2xl bg-black/10 z-1"
|
|
63
|
-
></div>
|
|
64
|
-
<span class="flex-1 text-base font-medium">{{
|
|
65
|
-
node.name
|
|
66
|
-
}}</span>
|
|
67
|
-
</div>
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@if (tab.properties.length === 0) {
|
|
71
|
-
<div class="py-8 text-center text-muted-color">
|
|
72
|
-
<p class="text-sm">
|
|
73
|
-
All {{ tab.title }} items are in use
|
|
74
|
-
</p>
|
|
75
|
-
</div>
|
|
76
|
-
}
|
|
77
|
-
</div>
|
|
78
|
-
</p-tabpanel>
|
|
79
|
-
}
|
|
80
|
-
</p-tabpanels>
|
|
81
|
-
</p-tabs>
|
|
82
|
-
}
|
|
83
|
-
</ng-template>
|
|
84
|
-
</mt-card>
|
|
85
|
-
|
|
86
|
-
<!-- Main Canvas Area -->
|
|
87
|
-
<div class="flex flex-col gap-4 flex-1 w-full h-full overflow-y-auto">
|
|
88
|
-
<mt-card>
|
|
89
|
-
<ng-template #headless>
|
|
90
|
-
<div class="p-4 flex items-center gap-2">
|
|
91
|
-
<mt-button
|
|
92
|
-
icon="layout.layout-top"
|
|
93
|
-
[label]="t('add-section')"
|
|
94
|
-
(onClick)="addSection()"
|
|
95
|
-
[disabled]="isLoading()"
|
|
96
|
-
></mt-button>
|
|
97
|
-
<mt-button
|
|
98
|
-
icon="general.eye"
|
|
99
|
-
[label]="t('preview')"
|
|
100
|
-
(onClick)="openPreview()"
|
|
101
|
-
[disabled]="isLoading()"
|
|
102
|
-
></mt-button>
|
|
103
|
-
<mt-button
|
|
104
|
-
icon="finance.credit-card-plus"
|
|
105
|
-
[label]="t('reset')"
|
|
106
|
-
(onClick)="resetFormConfiguration()"
|
|
107
|
-
[disabled]="isLoading()"
|
|
108
|
-
></mt-button>
|
|
109
|
-
</div>
|
|
110
|
-
</ng-template>
|
|
111
|
-
</mt-card>
|
|
112
|
-
|
|
113
|
-
@if (isLoading()) {
|
|
114
|
-
<!-- Form Loading Skeleton -->
|
|
115
|
-
@for (i of [1, 2]; track i) {
|
|
116
|
-
<mt-card>
|
|
117
|
-
<ng-template #headless>
|
|
118
|
-
<div class="p-4 space-y-4">
|
|
119
|
-
<!-- Section header skeleton -->
|
|
120
|
-
<div class="flex items-center justify-between">
|
|
121
|
-
<p-skeleton width="10rem" height="1.5rem" />
|
|
122
|
-
<div class="flex gap-2">
|
|
123
|
-
<p-skeleton width="2rem" height="2rem" shape="circle" />
|
|
124
|
-
<p-skeleton width="2rem" height="2rem" shape="circle" />
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
<!-- Fields skeleton -->
|
|
128
|
-
<div class="grid grid-cols-12 gap-4">
|
|
129
|
-
<div class="col-span-6">
|
|
130
|
-
<p-skeleton height="4rem" styleClass="rounded-lg" />
|
|
131
|
-
</div>
|
|
132
|
-
<div class="col-span-6">
|
|
133
|
-
<p-skeleton height="4rem" styleClass="rounded-lg" />
|
|
134
|
-
</div>
|
|
135
|
-
<div class="col-span-12">
|
|
136
|
-
<p-skeleton height="4rem" styleClass="rounded-lg" />
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
</ng-template>
|
|
141
|
-
</mt-card>
|
|
142
|
-
}
|
|
143
|
-
} @else {
|
|
144
|
-
@for (section of enrichedSections(); track section.id) {
|
|
145
|
-
<mt-fb-section
|
|
146
|
-
[section]="section"
|
|
147
|
-
[sectionsCount]="enrichedSections().length"
|
|
148
|
-
[allSections]="enrichedSections()"
|
|
149
|
-
(onFieldDrop)="drop($event)"
|
|
150
|
-
>
|
|
151
|
-
</mt-fb-section>
|
|
152
|
-
} @empty {
|
|
153
|
-
<mt-card>
|
|
154
|
-
<div class="h-27 p-4">
|
|
155
|
-
<div
|
|
156
|
-
class="flex justify-center items-center gap-4 h-full border-2 border-primary rounded-xl bg-primary-200 text-primary"
|
|
157
|
-
>
|
|
158
|
-
<span>{{ t("no-section") }}</span>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
</mt-card>
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
</ng-container>
|