fx-form-builder-wrapper 0.0.11
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/README.md +24 -0
- package/ng-package.json +7 -0
- package/package.json +16 -0
- package/src/lib/components/button/button.component.css +0 -0
- package/src/lib/components/button/button.component.html +1 -0
- package/src/lib/components/button/button.component.ts +24 -0
- package/src/lib/components/dynamic-table/dynamic-table.component.css +0 -0
- package/src/lib/components/dynamic-table/dynamic-table.component.html +69 -0
- package/src/lib/components/dynamic-table/dynamic-table.component.ts +201 -0
- package/src/lib/components/fx-form-component/fx-form-component.component.ts +64 -0
- package/src/lib/components/toggle/toggle.component.css +51 -0
- package/src/lib/components/toggle/toggle.component.html +12 -0
- package/src/lib/components/toggle/toggle.component.ts +33 -0
- package/src/lib/components/toggle-button/toggle-button.component.css +22 -0
- package/src/lib/components/toggle-button/toggle-button.component.html +10 -0
- package/src/lib/components/toggle-button/toggle-button.component.ts +40 -0
- package/src/lib/components/uploader/uploader.component.css +49 -0
- package/src/lib/components/uploader/uploader.component.html +23 -0
- package/src/lib/components/uploader/uploader.component.ts +59 -0
- package/src/lib/custom-controls/dispatch-to-clinic/dispatch-to-clinic.component.html +78 -0
- package/src/lib/custom-controls/dispatch-to-clinic/dispatch-to-clinic.component.ts +44 -0
- package/src/lib/fx-builder-wrapper.component.ts +64 -0
- package/src/lib/fx-builder-wrapper.service.ts +34 -0
- package/src/lib/panel/configuration-panel/configuration-panel.component.css +65 -0
- package/src/lib/panel/configuration-panel/configuration-panel.component.html +96 -0
- package/src/lib/panel/configuration-panel/configuration-panel.component.ts +90 -0
- package/src/lib/panel/settings-panel/settings-panel.component.css +30 -0
- package/src/lib/panel/settings-panel/settings-panel.component.html +28 -0
- package/src/lib/panel/settings-panel/settings-panel.component.ts +23 -0
- package/src/public-api.ts +7 -0
- package/src/styles/styles.css +22 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +15 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { ChangeDetectorRef, Component } from '@angular/core';
|
|
3
|
+
import { FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms';
|
|
4
|
+
import { FxBaseComponent, FxComponent, FxSelectSetting, FxSetting, FxStringSetting, FxValidation, FxValidatorService } from '@instantsys-labs/fx';
|
|
5
|
+
import { v4 as uuidv4} from 'uuid';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'fx-uploader',
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [CommonModule, FxComponent, FormsModule, ReactiveFormsModule],
|
|
11
|
+
templateUrl: './uploader.component.html',
|
|
12
|
+
styleUrl: './uploader.component.css'
|
|
13
|
+
})
|
|
14
|
+
export class UploaderComponent extends FxBaseComponent {
|
|
15
|
+
public uploadFileControl = new UntypedFormControl();
|
|
16
|
+
public uploadedFiles: Array<any> = [];
|
|
17
|
+
|
|
18
|
+
constructor(private cdr: ChangeDetectorRef) {
|
|
19
|
+
super(cdr)
|
|
20
|
+
this.onInit.subscribe((fxData)=>{
|
|
21
|
+
this._register(this.uploadFileControl);
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public onFileSelected(event: Event) {
|
|
26
|
+
const input = event.target as HTMLInputElement;
|
|
27
|
+
if (input.files) {
|
|
28
|
+
for(let i = 0; i < input?.files?.length; i++) {
|
|
29
|
+
const file = input.files[i];
|
|
30
|
+
const reader = new FileReader();
|
|
31
|
+
reader.onload = e => {
|
|
32
|
+
this.uploadedFiles.push({
|
|
33
|
+
file: file,
|
|
34
|
+
previewUrl: e.target?.result,
|
|
35
|
+
name: file?.name,
|
|
36
|
+
id: uuidv4()
|
|
37
|
+
})
|
|
38
|
+
this.uploadFileControl.setValue(this.uploadedFiles)
|
|
39
|
+
}
|
|
40
|
+
reader.readAsDataURL(file);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public deleteFile(id: string): void {
|
|
46
|
+
this.uploadedFiles = this.uploadedFiles.filter(file => file?.id !== id);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected settings(): FxSetting[] {
|
|
50
|
+
return [
|
|
51
|
+
new FxStringSetting({ key: 'upload-text', $title: 'Upload Text', value: 'Upload File'}),
|
|
52
|
+
new FxSelectSetting({key: 'multiple-upload', $title: 'Multiple Upload', value: false}, [{option: 'Enable', value: true}, {option: 'Disable', value: false}])
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected validations(): FxValidation[] {
|
|
57
|
+
return [FxValidatorService.required];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<fx-component [fxData]="fxData">
|
|
2
|
+
<section
|
|
3
|
+
class="justify-content-around lg:justify-content-between w-full white-color border-1 border-solid stroke_light_grey p-3 mb-3 mt-3">
|
|
4
|
+
<form [formGroup]="dispatchForm">
|
|
5
|
+
<div class="grid">
|
|
6
|
+
<!-- Courier Name -->
|
|
7
|
+
<div class="col-12 sm:col-6 md:col-3 input-container">
|
|
8
|
+
<label for="courierName" class="input-title">Courier Name</label>
|
|
9
|
+
<input autocomplete="off" formControlName="courierName" type="text" id="courierName"
|
|
10
|
+
name="courierName" class="p-inputtext p-component p-element input-field border-1 w-full"
|
|
11
|
+
placeholder="enter courier name" />
|
|
12
|
+
|
|
13
|
+
<!-- validation -->
|
|
14
|
+
<small *ngIf="dispatchForm.get('courierName')?.invalid && dispatchForm.get('courierName')?.touched"
|
|
15
|
+
class="text-danger-color block mt-1">
|
|
16
|
+
Courier Name is required.
|
|
17
|
+
</small>
|
|
18
|
+
<!-- validation -->
|
|
19
|
+
</div>
|
|
20
|
+
<!-- Courier Name -->
|
|
21
|
+
|
|
22
|
+
<!-- Tracking Number -->
|
|
23
|
+
<div class="col-12 sm:col-6 md:col-3 input-container">
|
|
24
|
+
<label for="trackingNumber" class="input-title">Tracking Number</label>
|
|
25
|
+
<input autocomplete="off" formControlName="trackingNumber" type="text" id="trackingNumber"
|
|
26
|
+
name="trackingNumber" class="p-inputtext p-component p-element input-field border-1 w-full"
|
|
27
|
+
placeholder="enter tracking number" />
|
|
28
|
+
<small
|
|
29
|
+
*ngIf="dispatchForm.get('trackingNumber')?.invalid && dispatchForm.get('trackingNumber')?.touched"
|
|
30
|
+
class="text-danger-color block mt-1">
|
|
31
|
+
Tracking Number is required.
|
|
32
|
+
</small>
|
|
33
|
+
</div>
|
|
34
|
+
<!-- Tracking Number -->
|
|
35
|
+
|
|
36
|
+
<!-- Tracking URL -->
|
|
37
|
+
<div class="col-12 sm:col-6 md:col-3 input-container">
|
|
38
|
+
<label for="trackingUrl" class="input-title">Tracking URL</label>
|
|
39
|
+
<input autocomplete="off" formControlName="trackingUrl" type="text" id="trackingUrl"
|
|
40
|
+
name="trackingUrl" class="p-inputtext p-component p-element input-field border-1 w-full"
|
|
41
|
+
placeholder="enter tracking url" />
|
|
42
|
+
<small *ngIf="dispatchForm.get('trackingUrl')?.invalid && dispatchForm.get('trackingUrl')?.touched"
|
|
43
|
+
class="text-danger-color block mt-1">
|
|
44
|
+
<span *ngIf="dispatchForm.get('trackingUrl')?.errors?.['required']">Tracking URL is
|
|
45
|
+
required.</span>
|
|
46
|
+
<span *ngIf="dispatchForm.get('trackingUrl')?.errors?.['pattern']">Invalid URL format.</span>
|
|
47
|
+
</small>
|
|
48
|
+
</div>
|
|
49
|
+
<!-- Tracking URL -->
|
|
50
|
+
|
|
51
|
+
<!-- Notes -->
|
|
52
|
+
<div class="col-12 md:col-6 input-container">
|
|
53
|
+
<label for="notes" class="input-title">Notes</label>
|
|
54
|
+
<textarea autocomplete="off" formControlName="notes" rows="5" id="notes" name="notes"
|
|
55
|
+
class="p-inputtext p-component p-element input-field border-1 w-full"
|
|
56
|
+
placeholder="enter notes"></textarea>
|
|
57
|
+
<small *ngIf="dispatchForm.get('notes')?.invalid && dispatchForm.get('notes')?.touched"
|
|
58
|
+
class="text-danger-color block mt-1">
|
|
59
|
+
Notes are required.
|
|
60
|
+
</small>
|
|
61
|
+
</div>
|
|
62
|
+
<!-- Notes -->
|
|
63
|
+
|
|
64
|
+
<!-- Address with Copy Icon -->
|
|
65
|
+
<div class="col-12 md:col-6 pt-0">
|
|
66
|
+
<div class="mb-1">Address</div>
|
|
67
|
+
<ng-container *ngIf="(clinicAddress$ | async) as address">
|
|
68
|
+
<address #completeAddress>{{address?.street}}, {{address?.state}}, {{address?.postalCode}}
|
|
69
|
+
<i class="pi pi-copy cursor-pointer text-xl text-secondary-color"
|
|
70
|
+
(click)="copyToClipboard(completeAddress.textContent)"></i>
|
|
71
|
+
</address>
|
|
72
|
+
</ng-container>
|
|
73
|
+
</div>
|
|
74
|
+
<!-- Address with Copy Icon -->
|
|
75
|
+
</div>
|
|
76
|
+
</form>
|
|
77
|
+
</section>
|
|
78
|
+
</fx-component>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { ChangeDetectorRef, Component, inject } from '@angular/core';
|
|
3
|
+
import { FormGroup, ReactiveFormsModule, Validators, FormBuilder, FormsModule } from '@angular/forms';
|
|
4
|
+
import { FxBaseComponent, FxComponent, FxSetting, FxStringSetting, FxValidation, FxValidatorService } from '@instantsys-labs/fx';
|
|
5
|
+
import { BehaviorSubject, take } from 'rxjs';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'lib-dispatch-to-clinic',
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [CommonModule, ReactiveFormsModule, FormsModule, FxComponent],
|
|
11
|
+
templateUrl: './dispatch-to-clinic.component.html'
|
|
12
|
+
})
|
|
13
|
+
export class DispatchToClinicComponent extends FxBaseComponent {
|
|
14
|
+
private fb = inject(FormBuilder);
|
|
15
|
+
|
|
16
|
+
public clinicAddress$: BehaviorSubject<any> = new BehaviorSubject<any>({});
|
|
17
|
+
|
|
18
|
+
public dispatchForm: FormGroup = this.fb.group({
|
|
19
|
+
courierName: ['', Validators.required],
|
|
20
|
+
trackingNumber: ['', Validators.required],
|
|
21
|
+
trackingUrl: ['', [Validators.required, Validators.pattern('https?://.+')]],
|
|
22
|
+
notes: ['', Validators.required]
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
constructor(private cdr: ChangeDetectorRef) {
|
|
26
|
+
super(cdr);
|
|
27
|
+
|
|
28
|
+
this.onInit.subscribe(() => {
|
|
29
|
+
this._register(this.dispatchForm);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected settings(): FxSetting[] {
|
|
34
|
+
return [new FxStringSetting({ key: 'heading-text', $title: 'Heading Text', value: 'My Default Value' })];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected validations(): FxValidation[] {
|
|
38
|
+
return [FxValidatorService.required];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public copyToClipboard(address: any): void {
|
|
42
|
+
navigator.clipboard.writeText(address);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
|
3
|
+
import { FxBuilderConfiguration, FxComponentBuilder, FxForm, FxMode, FxScope, FxUtils } from '@instantsys-labs/fx';
|
|
4
|
+
import { DispatchToClinicComponent } from './custom-controls/dispatch-to-clinic/dispatch-to-clinic.component';
|
|
5
|
+
import { FxBuilderWrapperService } from './fx-builder-wrapper.service';
|
|
6
|
+
import { DynamicTableComponent } from './components/dynamic-table/dynamic-table.component';
|
|
7
|
+
import { ToggleButtonComponent } from './components/toggle-button/toggle-button.component';
|
|
8
|
+
import { UploaderComponent } from './components/uploader/uploader.component';
|
|
9
|
+
import { ToggleComponent } from './components/toggle/toggle.component';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
selector: 'fx-builder-wrapper',
|
|
13
|
+
standalone: true,
|
|
14
|
+
imports: [CommonModule, FxComponentBuilder],
|
|
15
|
+
template: `
|
|
16
|
+
<fx-component-builder
|
|
17
|
+
#componentBuilder
|
|
18
|
+
[fx-form]="fxForm"
|
|
19
|
+
[configuration]="fxConfiguration"
|
|
20
|
+
[scope]="FxScope.BUILDER"
|
|
21
|
+
>
|
|
22
|
+
</fx-component-builder>
|
|
23
|
+
`,
|
|
24
|
+
styles: ``
|
|
25
|
+
})
|
|
26
|
+
export class FxBuilderWrapperComponent implements OnInit {
|
|
27
|
+
@ViewChild('componentBuilder') componentBuilder!: FxComponentBuilder;
|
|
28
|
+
@Input({ alias: 'fx-form', required: true }) fxForm: FxForm = FxUtils.createNewForm();
|
|
29
|
+
public fxMode: FxMode = FxMode.EDIT;
|
|
30
|
+
public fxConfiguration: FxBuilderConfiguration = {
|
|
31
|
+
settings: true,
|
|
32
|
+
logics: true,
|
|
33
|
+
customControls: true,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected readonly FxScope = FxScope;
|
|
37
|
+
protected readonly FxMode = FxMode;
|
|
38
|
+
|
|
39
|
+
constructor(private fxWrapperService: FxBuilderWrapperService) { }
|
|
40
|
+
|
|
41
|
+
public ngOnInit(): void {
|
|
42
|
+
if (!Boolean(this.fxWrapperService.getComponent('dispatch-to-clinic'))) {
|
|
43
|
+
this.fxWrapperService.registerCustomComponent('Dispatch To Clinic', 'dispatch-to-clinic', DispatchToClinicComponent);
|
|
44
|
+
}
|
|
45
|
+
if (!Boolean(this.fxWrapperService.getComponent('dynamic-table'))) {
|
|
46
|
+
this.fxWrapperService.registerCustomComponent('Dynamic Table', 'dynamic-table', DynamicTableComponent);
|
|
47
|
+
}
|
|
48
|
+
if (!Boolean(this.fxWrapperService.getComponent('toggle-button'))) {
|
|
49
|
+
this.fxWrapperService.registerCustomComponent('Toggle Button', 'toggle-button', ToggleButtonComponent);
|
|
50
|
+
}
|
|
51
|
+
if (!Boolean(this.fxWrapperService.getComponent('uploader'))) {
|
|
52
|
+
this.fxWrapperService.registerCustomComponent('Uploader', 'uploader', UploaderComponent);
|
|
53
|
+
}
|
|
54
|
+
if (!Boolean(this.fxWrapperService.getComponent('toggle'))) {
|
|
55
|
+
this.fxWrapperService.registerCustomComponent('Toggle', 'toggle', ToggleComponent);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
public getParsedForm(): FxForm {
|
|
60
|
+
return this.componentBuilder.getParsedForm();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Injectable, Type } from '@angular/core';
|
|
2
|
+
import { FxBaseComponent, FxComponentRegistryService } from '@instantsys-labs/fx';
|
|
3
|
+
import { BehaviorSubject, Subject } from 'rxjs';
|
|
4
|
+
|
|
5
|
+
@Injectable({
|
|
6
|
+
providedIn: 'root'
|
|
7
|
+
})
|
|
8
|
+
export class FxBuilderWrapperService {
|
|
9
|
+
public variables$ = new BehaviorSubject<any | null>(null);
|
|
10
|
+
constructor(private fxComponentRegistry: FxComponentRegistryService) { }
|
|
11
|
+
|
|
12
|
+
public registerCustomComponent(title: string, selector: string, component: Type<FxBaseComponent>
|
|
13
|
+
): void {
|
|
14
|
+
this.fxComponentRegistry.registerComponent(selector, component, {
|
|
15
|
+
registeringAs: "CUSTOM",
|
|
16
|
+
libraryItem: {
|
|
17
|
+
title,
|
|
18
|
+
icon: 'fa-eye',
|
|
19
|
+
fxData: {
|
|
20
|
+
id: null,
|
|
21
|
+
name: selector,
|
|
22
|
+
value: "",
|
|
23
|
+
selector: selector,
|
|
24
|
+
elements: [],
|
|
25
|
+
events: []
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public getComponent(selector: string): Type<FxBaseComponent> | undefined {
|
|
32
|
+
return this.fxComponentRegistry.getComponent(selector);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* .form__input {
|
|
2
|
+
width: clamp(120px, 50vw, 420px);
|
|
3
|
+
width: 100%;
|
|
4
|
+
height: 2.5rem;
|
|
5
|
+
padding: 0 1.25rem;
|
|
6
|
+
border: 1px solid black;
|
|
7
|
+
border-radius: 2px;
|
|
8
|
+
margin: 0.625rem auto;
|
|
9
|
+
transition: all 250ms;
|
|
10
|
+
} */
|
|
11
|
+
|
|
12
|
+
:host ::ng-deep .p-dialog {
|
|
13
|
+
.p-dialog-content {
|
|
14
|
+
padding: 1.5rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.p-dialog-header {
|
|
18
|
+
background: white;
|
|
19
|
+
padding: 1rem;
|
|
20
|
+
font-size: 1.25rem;
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.form__input {
|
|
26
|
+
width: 100%;
|
|
27
|
+
padding: 8px;
|
|
28
|
+
border: 1px solid #ccc;
|
|
29
|
+
border-radius: 5px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
label {
|
|
33
|
+
font-size: 14px;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
margin-bottom: 5px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
.button-group {
|
|
40
|
+
display: flex;
|
|
41
|
+
justify-content: flex-end;
|
|
42
|
+
gap: 10px;
|
|
43
|
+
padding-top: 1rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
button {
|
|
47
|
+
padding: 8px 12px;
|
|
48
|
+
border: none;
|
|
49
|
+
border-radius: 5px;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.cancel {
|
|
54
|
+
background: #ccc;
|
|
55
|
+
color: black;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.save {
|
|
59
|
+
background: #007bff;
|
|
60
|
+
color: white;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
button:hover {
|
|
64
|
+
opacity: 0.8;
|
|
65
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<p-dialog [modal]="true" [draggable]="false" [(visible)]="visible" [style]="{ width: '35rem' }">
|
|
2
|
+
<ng-content #header>
|
|
3
|
+
<div class="flex p-2 bg-white border-2">
|
|
4
|
+
<p class="text-base">Edit Configuration</p>
|
|
5
|
+
</div>
|
|
6
|
+
</ng-content>
|
|
7
|
+
|
|
8
|
+
<ng-template #content>
|
|
9
|
+
<div class="flex flex-col gap-4 bg-white p-2 border-2 border-gray-200">
|
|
10
|
+
<div class="flex items-center gap-4 mb-8">
|
|
11
|
+
<label for="rows" class="font-semibold w-24">Rows</label>
|
|
12
|
+
<input type="text" [readonly]="enableAPI" [(ngModel)]="rows" placeholder="rows" class="form__input" id="name" autocomplete="rows" />
|
|
13
|
+
</div>
|
|
14
|
+
<div class="flex items-center gap-4 mb-8">
|
|
15
|
+
<label for="enableAPI" class="font-semibold w-24">Enable API</label>
|
|
16
|
+
<input type="checkbox" [(ngModel)]="enableAPI" placeholder="API Url" id="enableAPI" autocomplete="enableAPI" />
|
|
17
|
+
</div>
|
|
18
|
+
@if (enableAPI) {
|
|
19
|
+
<div class="flex items-center gap-4 mb-8">
|
|
20
|
+
<label for="api" class="font-semibold w-24">API</label>
|
|
21
|
+
<input type="text" [(ngModel)]="api" placeholder="Enter API Url" id="api" autocomplete="api" />
|
|
22
|
+
</div>
|
|
23
|
+
}
|
|
24
|
+
<div class="flex items-center gap-4 mb-8">
|
|
25
|
+
<div class="grid grid-nogutter">
|
|
26
|
+
<div class="col-2 mb-3 flex items-center gap-3">
|
|
27
|
+
<p class="text-sm font-semibold">Columns:</p>
|
|
28
|
+
<button type="button" (click)="addColumn()">Add Column</button>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="col-10">
|
|
31
|
+
<form [formGroup]="dynamicForm" (ngSubmit)="onSubmit()">
|
|
32
|
+
<div formArrayName="columns">
|
|
33
|
+
<div *ngFor="let column of columns.controls; let i = index" [formGroupName]="i">
|
|
34
|
+
<div class="flex gap-4 mb-2">
|
|
35
|
+
<label class="white-space-nowrap">Column Name:</label>
|
|
36
|
+
<input formControlName="header" placeholder="Enter Column Name" class="form__input" />
|
|
37
|
+
</div>
|
|
38
|
+
<div class="flex gap-4">
|
|
39
|
+
<label>Column Type:</label>
|
|
40
|
+
<select formControlName="cellType">
|
|
41
|
+
<option *ngFor="let type of columnTypes" [value]="type">{{type}}</option>
|
|
42
|
+
</select>
|
|
43
|
+
</div>
|
|
44
|
+
@if (enableAPI) {
|
|
45
|
+
<div class="flex gap-4 mt-2">
|
|
46
|
+
<label>API Value Key:</label>
|
|
47
|
+
<input formControlName="apiKey" placeholder="Enter Value Key" class="form__input" />
|
|
48
|
+
</div>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
<!-- Show options if columnType is 'radio-group' or 'dropdown' -->
|
|
52
|
+
<div *ngIf="['radio-group', 'dropdown'].includes(column.value.cellType)">
|
|
53
|
+
<div formArrayName="options">
|
|
54
|
+
<div *ngFor="let option of getOptions(i).controls; let j = index" [formGroupName]="j">
|
|
55
|
+
<label>Option {{ j + 1 }}:</label>
|
|
56
|
+
<input formControlName="optionName" placeholder="Enter option name" class="form__input" />
|
|
57
|
+
<input formControlName="optionValue" placeholder="Enter option value" class="form__input" />
|
|
58
|
+
<button type="button" (click)="getOptions(i).removeAt(j)">Remove Option</button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<button type="button" (click)="addOption(i)">Add Option</button>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Show apiUrl if columnType is 'smart-dropdown' -->
|
|
65
|
+
<div *ngIf="['smart-dropdown'].includes(column.value.cellType)" class="flex gap-2">
|
|
66
|
+
<div>
|
|
67
|
+
<label>API Url</label>
|
|
68
|
+
<input formControlName="apiUrl" placeholder="Enter api url" class="form__input" />
|
|
69
|
+
</div>
|
|
70
|
+
<div>
|
|
71
|
+
<label>Value Key</label>
|
|
72
|
+
<input formControlName="valueKey" placeholder="Enter Value Key" class="form__input" />
|
|
73
|
+
</div>
|
|
74
|
+
<div>
|
|
75
|
+
<label>Label Key</label>
|
|
76
|
+
<input formControlName="labelKey" placeholder="Enter Label Key" class="form__input" />
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<button type="button" (click)="columns.removeAt(i)">Remove Column</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</form>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</ng-template>
|
|
89
|
+
|
|
90
|
+
<ng-template #footer>
|
|
91
|
+
<div class="flex justify-end bg-blue-500 gap-4 w-full">
|
|
92
|
+
<p-button label="Cancel" severity="secondary" (click)="closeDialog()" />
|
|
93
|
+
<p-button styleClass="border border-indigo-600" label="Save" (click)="saveConfiguration()" />
|
|
94
|
+
</div>
|
|
95
|
+
</ng-template>
|
|
96
|
+
</p-dialog>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
3
|
+
import { ButtonModule } from 'primeng/button';
|
|
4
|
+
import { Dialog } from 'primeng/dialog';
|
|
5
|
+
import { InputTextModule } from 'primeng/inputtext';
|
|
6
|
+
import { Card } from 'primeng/card';
|
|
7
|
+
import { FormArray, FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
8
|
+
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'fx-configuration-panel',
|
|
11
|
+
standalone: true,
|
|
12
|
+
imports: [CommonModule, ReactiveFormsModule, ButtonModule, Dialog, InputTextModule, FormsModule],
|
|
13
|
+
templateUrl: './configuration-panel.component.html',
|
|
14
|
+
styleUrl: './configuration-panel.component.css'
|
|
15
|
+
})
|
|
16
|
+
export class ConfigurationPanelComponent {
|
|
17
|
+
@Input() visible: boolean = false;
|
|
18
|
+
@Output() isVisible = new EventEmitter<boolean>();
|
|
19
|
+
@Output() configuration = new EventEmitter<any>();
|
|
20
|
+
|
|
21
|
+
public rows: number = 1;
|
|
22
|
+
public enableAPI: boolean = false;
|
|
23
|
+
public api: string = '';
|
|
24
|
+
|
|
25
|
+
public dynamicForm: FormGroup;
|
|
26
|
+
|
|
27
|
+
public columnTypes: string[] = ['text', 'input-text', 'input-number', 'dropdown', 'smart-dropdown', 'checkbox', 'radio', 'radio-group', 'file-upload', 'textarea'];
|
|
28
|
+
|
|
29
|
+
constructor(private fb: FormBuilder) {
|
|
30
|
+
this.dynamicForm = this.fb.group({
|
|
31
|
+
columns: this.fb.array([])
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get columns(): FormArray {
|
|
36
|
+
return this.dynamicForm.get('columns') as FormArray;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add Column Dynamically
|
|
40
|
+
public addColumn(): void {
|
|
41
|
+
const columnFormGroup = this.fb.group({
|
|
42
|
+
header: ['', Validators.required],
|
|
43
|
+
cellType: ['', Validators.required],
|
|
44
|
+
placeholder: '',
|
|
45
|
+
options: this.fb.array([]),
|
|
46
|
+
apiUrl: '',
|
|
47
|
+
valueKey: '',
|
|
48
|
+
labelKey: '',
|
|
49
|
+
className: '',
|
|
50
|
+
apiKey: ''
|
|
51
|
+
})
|
|
52
|
+
this.columns.push(columnFormGroup);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Remove Column
|
|
56
|
+
public removeColumn(index: number): void {
|
|
57
|
+
this.columns.removeAt(index);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add Options Dynamically
|
|
61
|
+
public addOption(columnIndex: number) {
|
|
62
|
+
const optionGroup = this.fb.group({
|
|
63
|
+
optionName: ['', Validators.required],
|
|
64
|
+
optionValue: ['', Validators.required]
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const column = this.columns.at(columnIndex) as FormGroup;
|
|
68
|
+
const options = column.get('options') as FormArray;
|
|
69
|
+
options.push(optionGroup);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Get options FormArray for a specific column
|
|
73
|
+
public getOptions(columnIndex: number) {
|
|
74
|
+
const column = this.columns.at(columnIndex) as FormGroup;
|
|
75
|
+
return column.get('options') as FormArray;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public closeDialog(): void {
|
|
79
|
+
this.isVisible.emit(false);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public saveConfiguration(): void {
|
|
83
|
+
this.configuration.emit({ rows: this.rows, columns: this.dynamicForm.value?.columns, enableAPI: this.enableAPI, api: this.api });
|
|
84
|
+
this.isVisible.emit(false);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public onSubmit(): void {
|
|
88
|
+
console.log("Value columns formArray", this.dynamicForm.value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.fx-element {
|
|
2
|
+
position: relative;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.fx-element .fx-overlay {
|
|
6
|
+
position: absolute;
|
|
7
|
+
top: 0;
|
|
8
|
+
left: 0;
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 100%;
|
|
11
|
+
background: rgba(0, 0, 0, 0.1); /* Light gray with 0.5 transparency */
|
|
12
|
+
z-index: -1; /* Place it below the content by default */
|
|
13
|
+
pointer-events: none; /* Allow interaction with the underlying content */
|
|
14
|
+
opacity: 0; /* Initially hidden */
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.fx-element:hover .fx-overlay {
|
|
18
|
+
z-index: 1; /* Bring the overlay above content */
|
|
19
|
+
opacity: 1; /* Make the overlay visible */
|
|
20
|
+
pointer-events: auto; /* Allow interaction with the overlay */
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.fx-element:hover .fx-overlay .fx-actions{
|
|
24
|
+
position: absolute;
|
|
25
|
+
margin-top: -26px;
|
|
26
|
+
height: 25px;
|
|
27
|
+
border-top-right-radius: 10px;
|
|
28
|
+
width: 100%;
|
|
29
|
+
border-top-left-radius: 10px;
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<fx-configuration-panel [visible]="visible" (isVisible)="visible = $event" (configuration)="configuration.emit($event)"></fx-configuration-panel>
|
|
2
|
+
|
|
3
|
+
<div class="fx-element">
|
|
4
|
+
<ng-content></ng-content>
|
|
5
|
+
<ng-container *ngIf="fxData.$fxForm?.$mode !== FxMode.VIEW">
|
|
6
|
+
<div class="fx-overlay border-gray-400 border rounded cursor-pointer" (click)="onElementSelect(fxData)"
|
|
7
|
+
(dblclick)="openSettingDialog()">
|
|
8
|
+
<div class="fx-actions flex justify-between">
|
|
9
|
+
|
|
10
|
+
<div class="bg-gray-700 text-gray-300 px-2 rounded-t ml-2 text-xs flex justify-center items-center">
|
|
11
|
+
<div>#{{ fxData.name }}-<span class="text-xs italic">{{ fxData.id }}</span></div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="flex justify-around items-end mr-2">
|
|
15
|
+
<div class="cursor-pointer bg-secondary text-white w-8 mr-1 text-center rounded-t" title="Settings"
|
|
16
|
+
(click)="fxSettingService.openSetting(fxData)">
|
|
17
|
+
<i class="fa fa-cog text-xs"></i>
|
|
18
|
+
</div>
|
|
19
|
+
<div (click)="deleteElement(fxData)"
|
|
20
|
+
class="cursor-pointer bg-red-600 text-white w-8 mr-1 text-center rounded-t" title="Delete">
|
|
21
|
+
<i class="fa fa-times text-xs"></i>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<ng-container #dynamicComponentContainer></ng-container>
|
|
27
|
+
</ng-container>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import { ChangeDetectorRef, Component, EventEmitter, inject, Input, OnInit, Output, ViewChild, ViewContainerRef } from '@angular/core';
|
|
3
|
+
import { FxComponent, FxData, FxMode, FxSettingComponent, FxSettingsService, FxUtils } from '@instantsys-labs/fx';
|
|
4
|
+
import { Dialog } from 'primeng/dialog';
|
|
5
|
+
import { ButtonModule } from 'primeng/button';
|
|
6
|
+
import { InputTextModule } from 'primeng/inputtext';
|
|
7
|
+
import { ConfigurationPanelComponent } from '../configuration-panel/configuration-panel.component';
|
|
8
|
+
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'fx-settings-panel',
|
|
11
|
+
standalone: true,
|
|
12
|
+
imports: [CommonModule, ButtonModule, InputTextModule, ConfigurationPanelComponent],
|
|
13
|
+
templateUrl: './settings-panel.component.html',
|
|
14
|
+
styleUrl: './settings-panel.component.css'
|
|
15
|
+
})
|
|
16
|
+
export class SettingsPanelComponent extends FxComponent implements OnInit {
|
|
17
|
+
@Output() configuration = new EventEmitter<any>();
|
|
18
|
+
public visible: boolean = false;
|
|
19
|
+
|
|
20
|
+
public openSettingDialog(): void {
|
|
21
|
+
this.visible = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Public API Surface of fx-builder-wrapper
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export * from './lib/fx-builder-wrapper.service';
|
|
6
|
+
export * from './lib/fx-builder-wrapper.component'; // builder // admin
|
|
7
|
+
export * from './lib/components/fx-form-component/fx-form-component.component'; // form preview
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@import "../node_modules/ckeditor5/dist/ckeditor5.css";
|
|
2
|
+
|
|
3
|
+
@tailwind base;
|
|
4
|
+
@tailwind components;
|
|
5
|
+
@tailwind utilities;
|
|
6
|
+
|
|
7
|
+
@layer base{
|
|
8
|
+
:root {
|
|
9
|
+
--color-primary: 21 83 165;
|
|
10
|
+
--color-secondary: 245 150 14;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.card {
|
|
14
|
+
@apply p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700 dark:text-gray-100;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ck-powered-by-balloon{
|
|
18
|
+
display: none !important;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
|
3
|
+
{
|
|
4
|
+
"extends": "../../tsconfig.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"outDir": "../../out-tsc/lib",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"inlineSources": true,
|
|
10
|
+
"types": []
|
|
11
|
+
},
|
|
12
|
+
"exclude": [
|
|
13
|
+
"**/*.spec.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|