commons-shared-web-ui 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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { NgModule, EventEmitter, Output,
|
|
2
|
+
import { NgModule, Input, Component, EventEmitter, HostListener, Output, forwardRef, Injectable, inject, LOCALE_ID, ViewChildren } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
|
-
import { CommonModule } from '@angular/common';
|
|
4
|
+
import { CommonModule, formatDate } from '@angular/common';
|
|
5
5
|
import { MatCardModule } from '@angular/material/card';
|
|
6
6
|
import * as i2$1 from '@angular/material/snack-bar';
|
|
7
7
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
|
@@ -44,7 +44,9 @@ import * as i1$2 from '@angular/forms';
|
|
|
44
44
|
import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
45
45
|
import * as i1$1 from '@angular/router';
|
|
46
46
|
import * as i3 from '@angular/common/http';
|
|
47
|
-
import {
|
|
47
|
+
import { HttpParams } from '@angular/common/http';
|
|
48
|
+
import { BehaviorSubject, Subject, combineLatest, forkJoin, of } from 'rxjs';
|
|
49
|
+
import { takeUntil, debounceTime, distinctUntilChanged, map, finalize, catchError } from 'rxjs/operators';
|
|
48
50
|
|
|
49
51
|
class MaterialModule {
|
|
50
52
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MaterialModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
@@ -225,81 +227,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
225
227
|
}]
|
|
226
228
|
}] });
|
|
227
229
|
|
|
228
|
-
class CardType1Component {
|
|
229
|
-
config;
|
|
230
|
-
theme = 'theme-1';
|
|
231
|
-
cardClick = new EventEmitter();
|
|
232
|
-
get valueStyle() {
|
|
233
|
-
return this.config.valueColor ? { color: this.config.valueColor } : {};
|
|
234
|
-
}
|
|
235
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: CardType1Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
236
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: CardType1Component, isStandalone: false, selector: "lib-card-type-1", inputs: { config: "config", theme: "theme" }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "<div [class]=\"'cc-card ' + theme\" [style.width]=\"config.width || '100%'\" [style.height]=\"config.height || '100%'\" (click)=\"cardClick.emit()\">\n <div class=\"card-content\">\n <div class=\"card-label\">{{ config.label }}</div>\n <div class=\"card-value\" [ngStyle]=\"valueStyle\">{{ config.value }}</div>\n <div\n *ngIf=\"config.subtext\"\n class=\"card-subtext\"\n [ngClass]=\"config.subtextClass\"\n >\n {{ config.subtext }}\n </div>\n </div>\n</div>\n", styles: [".cc-card{display:flex;flex-direction:column;justify-content:center;max-width:100%;box-sizing:border-box;overflow:hidden}.card-label{font-family:var(--cc-font-family);font-size:var(--cc-text-size-label);color:var(--cc-text-color-secondary);text-transform:uppercase;font-weight:var(--cc-text-weight-medium);margin-bottom:8px}.card-value{font-family:var(--cc-font-family);font-size:var(--cc-text-size-value);color:var(--cc-text-color-primary);font-weight:var(--cc-text-weight-bold);margin-bottom:4px}.card-subtext{font-family:var(--cc-font-family);font-size:12px;color:var(--cc-text-color-secondary)}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
|
|
237
|
-
}
|
|
238
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: CardType1Component, decorators: [{
|
|
239
|
-
type: Component,
|
|
240
|
-
args: [{ selector: 'lib-card-type-1', standalone: false, template: "<div [class]=\"'cc-card ' + theme\" [style.width]=\"config.width || '100%'\" [style.height]=\"config.height || '100%'\" (click)=\"cardClick.emit()\">\n <div class=\"card-content\">\n <div class=\"card-label\">{{ config.label }}</div>\n <div class=\"card-value\" [ngStyle]=\"valueStyle\">{{ config.value }}</div>\n <div\n *ngIf=\"config.subtext\"\n class=\"card-subtext\"\n [ngClass]=\"config.subtextClass\"\n >\n {{ config.subtext }}\n </div>\n </div>\n</div>\n", styles: [".cc-card{display:flex;flex-direction:column;justify-content:center;max-width:100%;box-sizing:border-box;overflow:hidden}.card-label{font-family:var(--cc-font-family);font-size:var(--cc-text-size-label);color:var(--cc-text-color-secondary);text-transform:uppercase;font-weight:var(--cc-text-weight-medium);margin-bottom:8px}.card-value{font-family:var(--cc-font-family);font-size:var(--cc-text-size-value);color:var(--cc-text-color-primary);font-weight:var(--cc-text-weight-bold);margin-bottom:4px}.card-subtext{font-family:var(--cc-font-family);font-size:12px;color:var(--cc-text-color-secondary)}\n"] }]
|
|
241
|
-
}], propDecorators: { config: [{
|
|
242
|
-
type: Input
|
|
243
|
-
}], theme: [{
|
|
244
|
-
type: Input
|
|
245
|
-
}], cardClick: [{
|
|
246
|
-
type: Output
|
|
247
|
-
}] } });
|
|
248
|
-
|
|
249
|
-
class CardType2Component {
|
|
250
|
-
config;
|
|
251
|
-
theme = 'theme-1';
|
|
252
|
-
cardClick = new EventEmitter();
|
|
253
|
-
get iconBoxStyle() {
|
|
254
|
-
if (this.config.iconUrl) {
|
|
255
|
-
return { backgroundColor: 'transparent' };
|
|
256
|
-
}
|
|
257
|
-
return this.config.iconBackgroundColor ? { backgroundColor: this.config.iconBackgroundColor } : {};
|
|
258
|
-
}
|
|
259
|
-
get iconStyle() {
|
|
260
|
-
return this.config.iconColor ? { color: this.config.iconColor } : {};
|
|
261
|
-
}
|
|
262
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: CardType2Component, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
263
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: CardType2Component, isStandalone: false, selector: "lib-card-type-2", inputs: { config: "config", theme: "theme" }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: "<div [class]=\"'cc-card ' + theme\" [style.width]=\"config.width || '100%'\" [style.height]=\"config.height || '100%'\" (click)=\"cardClick.emit()\">\n <div class=\"card-content-wrapper\">\n <div class=\"icon-box\" [ngStyle]=\"iconBoxStyle\">\n <img *ngIf=\"config.iconUrl; else matIconTpl\" [src]=\"config.iconUrl\" class=\"icon-image\" alt=\"icon\">\n <ng-template #matIconTpl>\n <mat-icon [ngStyle]=\"iconStyle\">{{ config.iconName }}</mat-icon>\n </ng-template>\n </div>\n <div class=\"text-content\">\n <div class=\"card-label\">{{ config.label }}</div>\n <div class=\"value-wrapper\">\n <span class=\"card-value\">{{ config.value }}</span>\n <span *ngIf=\"config.targetText\" class=\"card-target\">{{ config.targetText }}</span>\n </div>\n </div>\n </div>\n</div>\n", styles: [".cc-card{display:flex;flex-direction:column;justify-content:center;max-width:100%;box-sizing:border-box;overflow:hidden}.card-content-wrapper{display:flex;align-items:center;gap:16px}.icon-box{width:48px;height:48px;border-radius:8px;display:flex;align-items:center;justify-content:center;background-color:#fce8e6;color:#d32f2f}.icon-box mat-icon{font-size:24px;width:24px;height:24px}.icon-box .icon-image{object-fit:contain;border-radius:.5rem}.text-content{display:flex;flex-direction:column}.card-label{font-family:var(--cc-font-family);font-size:var(--cc-text-size-label);color:var(--cc-text-color-secondary);text-transform:uppercase;font-weight:var(--cc-text-weight-medium);margin-bottom:4px}.value-wrapper{display:flex;align-items:baseline;gap:8px}.card-value{font-family:var(--cc-font-family);font-size:var(--cc-text-size-value);color:var(--cc-text-color-primary);font-weight:var(--cc-text-weight-bold)}.card-target{font-family:var(--cc-font-family);font-size:12px;color:var(--cc-text-color-secondary)}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
|
|
264
|
-
}
|
|
265
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: CardType2Component, decorators: [{
|
|
266
|
-
type: Component,
|
|
267
|
-
args: [{ selector: 'lib-card-type-2', standalone: false, template: "<div [class]=\"'cc-card ' + theme\" [style.width]=\"config.width || '100%'\" [style.height]=\"config.height || '100%'\" (click)=\"cardClick.emit()\">\n <div class=\"card-content-wrapper\">\n <div class=\"icon-box\" [ngStyle]=\"iconBoxStyle\">\n <img *ngIf=\"config.iconUrl; else matIconTpl\" [src]=\"config.iconUrl\" class=\"icon-image\" alt=\"icon\">\n <ng-template #matIconTpl>\n <mat-icon [ngStyle]=\"iconStyle\">{{ config.iconName }}</mat-icon>\n </ng-template>\n </div>\n <div class=\"text-content\">\n <div class=\"card-label\">{{ config.label }}</div>\n <div class=\"value-wrapper\">\n <span class=\"card-value\">{{ config.value }}</span>\n <span *ngIf=\"config.targetText\" class=\"card-target\">{{ config.targetText }}</span>\n </div>\n </div>\n </div>\n</div>\n", styles: [".cc-card{display:flex;flex-direction:column;justify-content:center;max-width:100%;box-sizing:border-box;overflow:hidden}.card-content-wrapper{display:flex;align-items:center;gap:16px}.icon-box{width:48px;height:48px;border-radius:8px;display:flex;align-items:center;justify-content:center;background-color:#fce8e6;color:#d32f2f}.icon-box mat-icon{font-size:24px;width:24px;height:24px}.icon-box .icon-image{object-fit:contain;border-radius:.5rem}.text-content{display:flex;flex-direction:column}.card-label{font-family:var(--cc-font-family);font-size:var(--cc-text-size-label);color:var(--cc-text-color-secondary);text-transform:uppercase;font-weight:var(--cc-text-weight-medium);margin-bottom:4px}.value-wrapper{display:flex;align-items:baseline;gap:8px}.card-value{font-family:var(--cc-font-family);font-size:var(--cc-text-size-value);color:var(--cc-text-color-primary);font-weight:var(--cc-text-weight-bold)}.card-target{font-family:var(--cc-font-family);font-size:12px;color:var(--cc-text-color-secondary)}\n"] }]
|
|
268
|
-
}], propDecorators: { config: [{
|
|
269
|
-
type: Input
|
|
270
|
-
}], theme: [{
|
|
271
|
-
type: Input
|
|
272
|
-
}], cardClick: [{
|
|
273
|
-
type: Output
|
|
274
|
-
}] } });
|
|
275
|
-
|
|
276
|
-
class CardsModule {
|
|
277
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: CardsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
278
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: CardsModule, declarations: [CardType1Component,
|
|
279
|
-
CardType2Component], imports: [CommonModule,
|
|
280
|
-
MaterialModule], exports: [CardType1Component,
|
|
281
|
-
CardType2Component] });
|
|
282
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: CardsModule, imports: [CommonModule,
|
|
283
|
-
MaterialModule] });
|
|
284
|
-
}
|
|
285
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: CardsModule, decorators: [{
|
|
286
|
-
type: NgModule,
|
|
287
|
-
args: [{
|
|
288
|
-
declarations: [
|
|
289
|
-
CardType1Component,
|
|
290
|
-
CardType2Component
|
|
291
|
-
],
|
|
292
|
-
imports: [
|
|
293
|
-
CommonModule,
|
|
294
|
-
MaterialModule
|
|
295
|
-
],
|
|
296
|
-
exports: [
|
|
297
|
-
CardType1Component,
|
|
298
|
-
CardType2Component
|
|
299
|
-
]
|
|
300
|
-
}]
|
|
301
|
-
}] });
|
|
302
|
-
|
|
303
230
|
class AlertComponent {
|
|
304
231
|
variant = 'info';
|
|
305
232
|
title = '';
|
|
@@ -1553,12 +1480,10 @@ class ConfigurableFormModule {
|
|
|
1553
1480
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurableFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1554
1481
|
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: ConfigurableFormModule, imports: [CommonModule,
|
|
1555
1482
|
ReactiveFormsModule,
|
|
1556
|
-
HttpClientModule,
|
|
1557
1483
|
MaterialModule,
|
|
1558
1484
|
ConfigurableFormComponent], exports: [ConfigurableFormComponent] });
|
|
1559
1485
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ConfigurableFormModule, imports: [CommonModule,
|
|
1560
1486
|
ReactiveFormsModule,
|
|
1561
|
-
HttpClientModule,
|
|
1562
1487
|
MaterialModule,
|
|
1563
1488
|
ConfigurableFormComponent] });
|
|
1564
1489
|
}
|
|
@@ -1569,7 +1494,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
1569
1494
|
imports: [
|
|
1570
1495
|
CommonModule,
|
|
1571
1496
|
ReactiveFormsModule,
|
|
1572
|
-
HttpClientModule,
|
|
1573
1497
|
MaterialModule,
|
|
1574
1498
|
ConfigurableFormComponent
|
|
1575
1499
|
],
|
|
@@ -1579,24 +1503,798 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
1579
1503
|
}]
|
|
1580
1504
|
}] });
|
|
1581
1505
|
|
|
1506
|
+
class SmartFormController {
|
|
1507
|
+
formData = {};
|
|
1508
|
+
fieldSubjects = new Map();
|
|
1509
|
+
initialize(initialData) {
|
|
1510
|
+
this.formData = { ...initialData };
|
|
1511
|
+
this.fieldSubjects.forEach((subject, key) => {
|
|
1512
|
+
subject.next(this.formData[key]);
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
updateField(name, value) {
|
|
1516
|
+
this.formData[name] = value;
|
|
1517
|
+
if (!this.fieldSubjects.has(name)) {
|
|
1518
|
+
this.fieldSubjects.set(name, new BehaviorSubject(value));
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
this.fieldSubjects.get(name).next(value);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
getFieldValue(name) {
|
|
1525
|
+
return this.formData[name];
|
|
1526
|
+
}
|
|
1527
|
+
getFieldObservable(name) {
|
|
1528
|
+
if (!this.fieldSubjects.has(name)) {
|
|
1529
|
+
this.fieldSubjects.set(name, new BehaviorSubject(this.formData[name]));
|
|
1530
|
+
}
|
|
1531
|
+
return this.fieldSubjects.get(name).asObservable();
|
|
1532
|
+
}
|
|
1533
|
+
getAllData() {
|
|
1534
|
+
return { ...this.formData };
|
|
1535
|
+
}
|
|
1536
|
+
reset() {
|
|
1537
|
+
this.formData = {};
|
|
1538
|
+
this.fieldSubjects.forEach(subject => subject.next(undefined));
|
|
1539
|
+
}
|
|
1540
|
+
destroy() {
|
|
1541
|
+
this.fieldSubjects.forEach(subject => subject.complete());
|
|
1542
|
+
this.fieldSubjects.clear();
|
|
1543
|
+
}
|
|
1544
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormController, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1545
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormController });
|
|
1546
|
+
}
|
|
1547
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormController, decorators: [{
|
|
1548
|
+
type: Injectable
|
|
1549
|
+
}] });
|
|
1550
|
+
|
|
1551
|
+
class ExpressionService {
|
|
1552
|
+
loadedFunctions = new Map();
|
|
1553
|
+
evaluate(expression, context, variables) {
|
|
1554
|
+
try {
|
|
1555
|
+
const filteredContext = variables
|
|
1556
|
+
? Object.fromEntries(Object.entries(context).filter(([key]) => variables.includes(key)))
|
|
1557
|
+
: context;
|
|
1558
|
+
const func = new Function(...Object.keys(filteredContext), `return ${expression};`);
|
|
1559
|
+
return func(...Object.values(filteredContext));
|
|
1560
|
+
}
|
|
1561
|
+
catch (e) {
|
|
1562
|
+
console.error('Expression evaluation error:', e);
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
evaluateCondition(expression, context) {
|
|
1567
|
+
try {
|
|
1568
|
+
const func = new Function(...Object.keys(context), `return ${expression};`);
|
|
1569
|
+
const result = func(...Object.values(context));
|
|
1570
|
+
return !!result;
|
|
1571
|
+
}
|
|
1572
|
+
catch (e) {
|
|
1573
|
+
console.error('Condition evaluation error:', e);
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
evaluateFormula(formula, functionName, context, variables) {
|
|
1578
|
+
try {
|
|
1579
|
+
if (!this.loadedFunctions.has(functionName)) {
|
|
1580
|
+
const func = new Function('context', `
|
|
1581
|
+
${formula}
|
|
1582
|
+
return ${functionName};
|
|
1583
|
+
`)(context);
|
|
1584
|
+
this.loadedFunctions.set(functionName, func);
|
|
1585
|
+
}
|
|
1586
|
+
const fn = this.loadedFunctions.get(functionName);
|
|
1587
|
+
if (!fn)
|
|
1588
|
+
return null;
|
|
1589
|
+
const args = variables ? variables.map(v => context[v]) : [];
|
|
1590
|
+
return fn(...args);
|
|
1591
|
+
}
|
|
1592
|
+
catch (e) {
|
|
1593
|
+
console.error('Formula evaluation error:', e);
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
extractVariables(expression) {
|
|
1598
|
+
const regex = /\b[a-zA-Z_]\w*\b/g;
|
|
1599
|
+
const matches = expression.match(regex) || [];
|
|
1600
|
+
const keywords = ['true', 'false', 'null', 'undefined', 'return', 'if', 'else', 'for', 'while', 'function', 'var', 'let', 'const'];
|
|
1601
|
+
return [...new Set(matches.filter(m => !keywords.includes(m)))];
|
|
1602
|
+
}
|
|
1603
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ExpressionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1604
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ExpressionService, providedIn: 'root' });
|
|
1605
|
+
}
|
|
1606
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ExpressionService, decorators: [{
|
|
1607
|
+
type: Injectable,
|
|
1608
|
+
args: [{
|
|
1609
|
+
providedIn: 'root'
|
|
1610
|
+
}]
|
|
1611
|
+
}] });
|
|
1612
|
+
|
|
1613
|
+
class ValidationUtils {
|
|
1614
|
+
static email() {
|
|
1615
|
+
return (control) => {
|
|
1616
|
+
if (!control.value)
|
|
1617
|
+
return null;
|
|
1618
|
+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
1619
|
+
return emailRegex.test(control.value) ? null : { email: true };
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
static phone() {
|
|
1623
|
+
return (control) => {
|
|
1624
|
+
if (!control.value)
|
|
1625
|
+
return null;
|
|
1626
|
+
const phoneRegex = /^\+?[\d\s\-\(\)]{10,}$/;
|
|
1627
|
+
return phoneRegex.test(control.value) ? null : { phone: true };
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
static url() {
|
|
1631
|
+
return (control) => {
|
|
1632
|
+
if (!control.value)
|
|
1633
|
+
return null;
|
|
1634
|
+
const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/;
|
|
1635
|
+
return urlRegex.test(control.value) ? null : { url: true };
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
static minLength(min) {
|
|
1639
|
+
return (control) => {
|
|
1640
|
+
if (!control.value)
|
|
1641
|
+
return null;
|
|
1642
|
+
return control.value.length >= min ? null : { minLength: { min, actual: control.value.length } };
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
static maxLength(max) {
|
|
1646
|
+
return (control) => {
|
|
1647
|
+
if (!control.value)
|
|
1648
|
+
return null;
|
|
1649
|
+
return control.value.length <= max ? null : { maxLength: { max, actual: control.value.length } };
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
static pattern(pattern, message) {
|
|
1653
|
+
return (control) => {
|
|
1654
|
+
if (!control.value)
|
|
1655
|
+
return null;
|
|
1656
|
+
const regex = new RegExp(pattern);
|
|
1657
|
+
return regex.test(control.value) ? null : { pattern: { message: message || 'Invalid format' } };
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
static numberRange(min, max) {
|
|
1661
|
+
return (control) => {
|
|
1662
|
+
if (!control.value && control.value !== 0)
|
|
1663
|
+
return null;
|
|
1664
|
+
const value = Number(control.value);
|
|
1665
|
+
if (min !== undefined && value < min) {
|
|
1666
|
+
return { min: { min, actual: value } };
|
|
1667
|
+
}
|
|
1668
|
+
if (max !== undefined && value > max) {
|
|
1669
|
+
return { max: { max, actual: value } };
|
|
1670
|
+
}
|
|
1671
|
+
return null;
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
static dateRange(minDate, maxDate) {
|
|
1675
|
+
return (control) => {
|
|
1676
|
+
if (!control.value)
|
|
1677
|
+
return null;
|
|
1678
|
+
const date = new Date(control.value);
|
|
1679
|
+
if (minDate && date < new Date(minDate)) {
|
|
1680
|
+
return { minDate: { minDate } };
|
|
1681
|
+
}
|
|
1682
|
+
if (maxDate && date > new Date(maxDate)) {
|
|
1683
|
+
return { maxDate: { maxDate } };
|
|
1684
|
+
}
|
|
1685
|
+
return null;
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
static getErrorMessage(errors) {
|
|
1689
|
+
if (!errors)
|
|
1690
|
+
return '';
|
|
1691
|
+
if (errors['required'])
|
|
1692
|
+
return 'This field is required';
|
|
1693
|
+
if (errors['email'])
|
|
1694
|
+
return 'Please enter a valid email address';
|
|
1695
|
+
if (errors['phone'])
|
|
1696
|
+
return 'Please enter a valid phone number';
|
|
1697
|
+
if (errors['url'])
|
|
1698
|
+
return 'Please enter a valid URL';
|
|
1699
|
+
if (errors['minLength'])
|
|
1700
|
+
return `Minimum length is ${errors['minLength'].min} characters`;
|
|
1701
|
+
if (errors['maxLength'])
|
|
1702
|
+
return `Maximum length is ${errors['maxLength'].max} characters`;
|
|
1703
|
+
if (errors['min'])
|
|
1704
|
+
return `Minimum value is ${errors['min'].min}`;
|
|
1705
|
+
if (errors['max'])
|
|
1706
|
+
return `Maximum value is ${errors['max'].max}`;
|
|
1707
|
+
if (errors['minDate'])
|
|
1708
|
+
return `Date must be after ${errors['minDate'].minDate}`;
|
|
1709
|
+
if (errors['maxDate'])
|
|
1710
|
+
return `Date must be before ${errors['maxDate'].maxDate}`;
|
|
1711
|
+
if (errors['pattern'])
|
|
1712
|
+
return errors['pattern'].message || 'Invalid format';
|
|
1713
|
+
return 'Invalid value';
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
class FormFieldComponent {
|
|
1718
|
+
expressionService;
|
|
1719
|
+
http;
|
|
1720
|
+
config;
|
|
1721
|
+
controller;
|
|
1722
|
+
sectionIndex;
|
|
1723
|
+
value;
|
|
1724
|
+
isVisible = true;
|
|
1725
|
+
errorMessage = '';
|
|
1726
|
+
destroy$ = new Subject();
|
|
1727
|
+
constructor(expressionService, http) {
|
|
1728
|
+
this.expressionService = expressionService;
|
|
1729
|
+
this.http = http;
|
|
1730
|
+
}
|
|
1731
|
+
ngOnInit() {
|
|
1732
|
+
this.setupField();
|
|
1733
|
+
this.setupVisibility();
|
|
1734
|
+
this.setupGeneratedField();
|
|
1735
|
+
this.setupDependencies();
|
|
1736
|
+
// Initial load if no dependencies or if dependencies have initial values
|
|
1737
|
+
if (!this.config.optionConfig?.dependencies) {
|
|
1738
|
+
this.loadDropdownOptions();
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
ngOnDestroy() {
|
|
1742
|
+
this.destroy$.next();
|
|
1743
|
+
this.destroy$.complete();
|
|
1744
|
+
}
|
|
1745
|
+
setupField() {
|
|
1746
|
+
if (this.config.name) {
|
|
1747
|
+
const fieldName = this.getFieldName();
|
|
1748
|
+
this.controller.getFieldObservable(fieldName)
|
|
1749
|
+
.pipe(takeUntil(this.destroy$))
|
|
1750
|
+
.subscribe(val => {
|
|
1751
|
+
this.value = val;
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
setupVisibility() {
|
|
1756
|
+
if (this.config.visibilityExpression) {
|
|
1757
|
+
const variables = this.expressionService.extractVariables(this.config.visibilityExpression);
|
|
1758
|
+
const observables = variables.map(v => this.controller.getFieldObservable(v));
|
|
1759
|
+
combineLatest(observables)
|
|
1760
|
+
.pipe(takeUntil(this.destroy$))
|
|
1761
|
+
.subscribe(() => {
|
|
1762
|
+
const context = this.controller.getAllData();
|
|
1763
|
+
this.isVisible = this.expressionService.evaluateCondition(this.config.visibilityExpression, context);
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
setupGeneratedField() {
|
|
1768
|
+
if (this.config.type === 'GENERATED' && this.config.generatedConfig) {
|
|
1769
|
+
const variables = this.config.generatedConfig.variables || [];
|
|
1770
|
+
const observables = variables.map(v => this.controller.getFieldObservable(v));
|
|
1771
|
+
combineLatest(observables)
|
|
1772
|
+
.pipe(takeUntil(this.destroy$))
|
|
1773
|
+
.subscribe(() => {
|
|
1774
|
+
const context = this.controller.getAllData();
|
|
1775
|
+
const result = this.evaluateFormula(context);
|
|
1776
|
+
if (result !== null && this.config.name) {
|
|
1777
|
+
this.controller.updateField(this.getFieldName(), result);
|
|
1778
|
+
}
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
evaluateFormula(context) {
|
|
1783
|
+
if (!this.config.generatedConfig)
|
|
1784
|
+
return null;
|
|
1785
|
+
const formula = this.config.generatedConfig.formula;
|
|
1786
|
+
const functionName = this.extractFunctionName(formula);
|
|
1787
|
+
if (functionName) {
|
|
1788
|
+
return this.expressionService.evaluateFormula(formula, functionName, context, this.config.generatedConfig.variables);
|
|
1789
|
+
}
|
|
1790
|
+
return null;
|
|
1791
|
+
}
|
|
1792
|
+
extractFunctionName(formula) {
|
|
1793
|
+
const match = formula.match(/(?:function|fun)\s+(\w+)\s*\(/);
|
|
1794
|
+
return match ? match[1] : null;
|
|
1795
|
+
}
|
|
1796
|
+
setupDependencies() {
|
|
1797
|
+
if (!this.config.optionConfig?.dependencies)
|
|
1798
|
+
return;
|
|
1799
|
+
const dependencies = this.config.optionConfig.dependencies;
|
|
1800
|
+
const observables = Object.values(dependencies).map(fieldName => this.controller.getFieldObservable(fieldName));
|
|
1801
|
+
combineLatest(observables)
|
|
1802
|
+
.pipe(takeUntil(this.destroy$))
|
|
1803
|
+
.subscribe((values) => {
|
|
1804
|
+
// Create a map of dependency values
|
|
1805
|
+
const dependencyValues = {};
|
|
1806
|
+
Object.keys(dependencies).forEach((paramKey, index) => {
|
|
1807
|
+
dependencyValues[paramKey] = values[index];
|
|
1808
|
+
});
|
|
1809
|
+
// Check if all required dependencies have values (optional: could skip this if partial dependencies are allowed)
|
|
1810
|
+
const allPresent = Object.values(dependencyValues).every(v => v !== null && v !== undefined && v !== '');
|
|
1811
|
+
if (allPresent) {
|
|
1812
|
+
this.loadDropdownOptions(dependencyValues);
|
|
1813
|
+
}
|
|
1814
|
+
else {
|
|
1815
|
+
// Clear options if dependencies are missing
|
|
1816
|
+
if (this.config.optionConfig) {
|
|
1817
|
+
this.config.optionConfig.optionList = [];
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
loadDropdownOptions(queryParams = {}) {
|
|
1823
|
+
const optionConfig = this.config.optionConfig;
|
|
1824
|
+
const urls = optionConfig?.apiUrls || (optionConfig?.apiUrl ? [optionConfig.apiUrl] : optionConfig?.optionUrl ? [optionConfig.optionUrl] : []);
|
|
1825
|
+
if (!urls || urls.length === 0)
|
|
1826
|
+
return;
|
|
1827
|
+
// Create observables for each URL with query params
|
|
1828
|
+
const observables = urls.map(url => {
|
|
1829
|
+
let fullUrl = url;
|
|
1830
|
+
const params = new URLSearchParams();
|
|
1831
|
+
Object.entries(queryParams).forEach(([key, value]) => {
|
|
1832
|
+
if (value !== null && value !== undefined) {
|
|
1833
|
+
params.append(key, String(value));
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
const queryString = params.toString();
|
|
1837
|
+
if (queryString) {
|
|
1838
|
+
fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
|
|
1839
|
+
}
|
|
1840
|
+
return this.http.get(fullUrl);
|
|
1841
|
+
});
|
|
1842
|
+
forkJoin(observables)
|
|
1843
|
+
.pipe(takeUntil(this.destroy$))
|
|
1844
|
+
.subscribe({
|
|
1845
|
+
next: (responses) => {
|
|
1846
|
+
let mergedData = [];
|
|
1847
|
+
responses.forEach(response => {
|
|
1848
|
+
// Identify array source for each response
|
|
1849
|
+
const data = optionConfig?.dataPath
|
|
1850
|
+
? this.getValueByPath(response, optionConfig.dataPath)
|
|
1851
|
+
: (Array.isArray(response) ? response : response.data || response.items || []);
|
|
1852
|
+
if (Array.isArray(data)) {
|
|
1853
|
+
mergedData = [...mergedData, ...data];
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
// Sort merged data BEFORE mapping
|
|
1857
|
+
if (optionConfig?.sortBy) {
|
|
1858
|
+
const sortKey = optionConfig.sortBy;
|
|
1859
|
+
const direction = optionConfig.sortDirection === 'DESC' ? -1 : 1;
|
|
1860
|
+
mergedData.sort((a, b) => {
|
|
1861
|
+
const valA = this.getValueByPath(a, sortKey);
|
|
1862
|
+
const valB = this.getValueByPath(b, sortKey);
|
|
1863
|
+
if (typeof valA === 'string' && typeof valB === 'string') {
|
|
1864
|
+
return direction * valA.localeCompare(valB);
|
|
1865
|
+
}
|
|
1866
|
+
if (typeof valA === 'number' && typeof valB === 'number') {
|
|
1867
|
+
return direction * (valA - valB);
|
|
1868
|
+
}
|
|
1869
|
+
return 0;
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
this.config.optionConfig.optionList = mergedData.map((item) => {
|
|
1873
|
+
const label = optionConfig?.labelPath
|
|
1874
|
+
? this.getValueByPath(item, optionConfig.labelPath)
|
|
1875
|
+
: (item.label || item.name);
|
|
1876
|
+
const code = optionConfig?.valuePath
|
|
1877
|
+
? this.getValueByPath(item, optionConfig.valuePath)
|
|
1878
|
+
: (item.code || item.id || item.value);
|
|
1879
|
+
return {
|
|
1880
|
+
label: String(label),
|
|
1881
|
+
code: code,
|
|
1882
|
+
value: item
|
|
1883
|
+
};
|
|
1884
|
+
});
|
|
1885
|
+
},
|
|
1886
|
+
error: (err) => console.error('Failed to load dropdown options:', err)
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
getValueByPath(obj, path) {
|
|
1890
|
+
if (!path || path === '')
|
|
1891
|
+
return obj;
|
|
1892
|
+
return path.split('.').reduce((acc, part) => {
|
|
1893
|
+
const match = part.match(/(\w+)\[(\d+)\]/);
|
|
1894
|
+
if (match) {
|
|
1895
|
+
return acc?.[match[1]]?.[parseInt(match[2])];
|
|
1896
|
+
}
|
|
1897
|
+
return acc?.[part];
|
|
1898
|
+
}, obj);
|
|
1899
|
+
}
|
|
1900
|
+
onValueChange(event) {
|
|
1901
|
+
if (!this.config.name)
|
|
1902
|
+
return;
|
|
1903
|
+
const fieldName = this.getFieldName();
|
|
1904
|
+
let newValue;
|
|
1905
|
+
if (event?.target) {
|
|
1906
|
+
const target = event.target;
|
|
1907
|
+
if (target.type === 'checkbox') {
|
|
1908
|
+
newValue = target.checked;
|
|
1909
|
+
}
|
|
1910
|
+
else if (target.type === 'number') {
|
|
1911
|
+
newValue = target.value ? Number(target.value) : null;
|
|
1912
|
+
}
|
|
1913
|
+
else {
|
|
1914
|
+
newValue = target.value;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
else {
|
|
1918
|
+
newValue = event;
|
|
1919
|
+
}
|
|
1920
|
+
this.controller.updateField(fieldName, newValue);
|
|
1921
|
+
this.validate(newValue);
|
|
1922
|
+
}
|
|
1923
|
+
onCheckboxListChange(code, checked) {
|
|
1924
|
+
if (!this.config.name)
|
|
1925
|
+
return;
|
|
1926
|
+
const fieldName = this.getFieldName();
|
|
1927
|
+
const currentValue = this.controller.getFieldValue(fieldName) || [];
|
|
1928
|
+
let newValue;
|
|
1929
|
+
if (checked) {
|
|
1930
|
+
newValue = [...currentValue, code];
|
|
1931
|
+
}
|
|
1932
|
+
else {
|
|
1933
|
+
newValue = currentValue.filter((c) => c !== code);
|
|
1934
|
+
}
|
|
1935
|
+
this.controller.updateField(fieldName, newValue);
|
|
1936
|
+
this.validate(newValue);
|
|
1937
|
+
}
|
|
1938
|
+
isChecked(code) {
|
|
1939
|
+
const value = this.value || [];
|
|
1940
|
+
return Array.isArray(value) && value.includes(code);
|
|
1941
|
+
}
|
|
1942
|
+
validate(value) {
|
|
1943
|
+
this.errorMessage = '';
|
|
1944
|
+
if (this.config.required && !value) {
|
|
1945
|
+
this.errorMessage = 'This field is required';
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
if (!value)
|
|
1949
|
+
return;
|
|
1950
|
+
if (this.config.subType === 'EMAIL' && !this.config.textConfig?.pattern) {
|
|
1951
|
+
const validator = ValidationUtils.email();
|
|
1952
|
+
const errors = validator({ value });
|
|
1953
|
+
if (errors)
|
|
1954
|
+
this.errorMessage = ValidationUtils.getErrorMessage(errors);
|
|
1955
|
+
}
|
|
1956
|
+
if (this.config.subType === 'PHONE' && !this.config.textConfig?.pattern) {
|
|
1957
|
+
const validator = ValidationUtils.phone();
|
|
1958
|
+
const errors = validator({ value });
|
|
1959
|
+
if (errors)
|
|
1960
|
+
this.errorMessage = ValidationUtils.getErrorMessage(errors);
|
|
1961
|
+
}
|
|
1962
|
+
if (this.config.textConfig?.length) {
|
|
1963
|
+
const { min, max } = this.config.textConfig.length;
|
|
1964
|
+
if (min && value.length < min) {
|
|
1965
|
+
this.errorMessage = `Minimum length is ${min} characters`;
|
|
1966
|
+
}
|
|
1967
|
+
if (max && value.length > max) {
|
|
1968
|
+
this.errorMessage = `Maximum length is ${max} characters`;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
if (this.config.textConfig?.pattern) {
|
|
1972
|
+
try {
|
|
1973
|
+
const regex = new RegExp(this.config.textConfig.pattern);
|
|
1974
|
+
if (!regex.test(value)) {
|
|
1975
|
+
this.errorMessage = this.config.textConfig.patternMessage || 'Invalid format';
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
catch (e) {
|
|
1979
|
+
console.error('Invalid regex pattern:', this.config.textConfig.pattern);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
if (this.config.numberConfig) {
|
|
1983
|
+
const { min, max } = this.config.numberConfig;
|
|
1984
|
+
const numValue = Number(value);
|
|
1985
|
+
if (min !== undefined && numValue < min) {
|
|
1986
|
+
this.errorMessage = `Minimum value is ${min}`;
|
|
1987
|
+
}
|
|
1988
|
+
if (max !== undefined && numValue > max) {
|
|
1989
|
+
this.errorMessage = `Maximum value is ${max}`;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
getFieldName() {
|
|
1994
|
+
if (this.sectionIndex !== undefined && this.config.name) {
|
|
1995
|
+
return `${this.config.name}_${this.sectionIndex}`;
|
|
1996
|
+
}
|
|
1997
|
+
return this.config.name || '';
|
|
1998
|
+
}
|
|
1999
|
+
get isTextField() {
|
|
2000
|
+
return this.config.type === 'TEXT_INPUT';
|
|
2001
|
+
}
|
|
2002
|
+
get isNumberField() {
|
|
2003
|
+
return this.config.type === 'NUMBER_INPUT';
|
|
2004
|
+
}
|
|
2005
|
+
get isDateField() {
|
|
2006
|
+
return this.config.type === 'DATE';
|
|
2007
|
+
}
|
|
2008
|
+
get isDropdown() {
|
|
2009
|
+
return this.config.type === 'DROPDOWN';
|
|
2010
|
+
}
|
|
2011
|
+
get isRadio() {
|
|
2012
|
+
return this.config.type === 'RADIO';
|
|
2013
|
+
}
|
|
2014
|
+
get isCheckbox() {
|
|
2015
|
+
return this.config.type === 'CHECKBOX';
|
|
2016
|
+
}
|
|
2017
|
+
get isChip() {
|
|
2018
|
+
return this.config.type === 'CHIP';
|
|
2019
|
+
}
|
|
2020
|
+
get isSwitch() {
|
|
2021
|
+
return this.config.type === 'SWITCH';
|
|
2022
|
+
}
|
|
2023
|
+
get isRating() {
|
|
2024
|
+
return this.config.type === 'RATING';
|
|
2025
|
+
}
|
|
2026
|
+
onRatingChange(star, event) {
|
|
2027
|
+
if (!this.config.name || this.config.disabled)
|
|
2028
|
+
return;
|
|
2029
|
+
let newValue = star;
|
|
2030
|
+
if (this.config.ratingConfig?.allowHalf && event) {
|
|
2031
|
+
const target = event.target;
|
|
2032
|
+
const rect = target.getBoundingClientRect();
|
|
2033
|
+
const x = event.clientX - rect.left;
|
|
2034
|
+
// If click is in the first 50% of the star, it's a half star
|
|
2035
|
+
if (x < rect.width / 2) {
|
|
2036
|
+
newValue = star - 0.5;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
// Toggle off if clicking same value
|
|
2040
|
+
if (this.value === newValue) {
|
|
2041
|
+
newValue = 0;
|
|
2042
|
+
}
|
|
2043
|
+
this.controller.updateField(this.getFieldName(), newValue);
|
|
2044
|
+
this.validate(newValue);
|
|
2045
|
+
}
|
|
2046
|
+
getStarArray() {
|
|
2047
|
+
const max = this.config.ratingConfig?.maxRating || 5;
|
|
2048
|
+
return Array.from({ length: max }, (_, i) => i + 1);
|
|
2049
|
+
}
|
|
2050
|
+
isStarHalf(star) {
|
|
2051
|
+
const value = this.value || 0;
|
|
2052
|
+
return value === star - 0.5;
|
|
2053
|
+
}
|
|
2054
|
+
isStarFilled(star) {
|
|
2055
|
+
const value = this.value || 0;
|
|
2056
|
+
return value >= star;
|
|
2057
|
+
}
|
|
2058
|
+
get isGenerated() {
|
|
2059
|
+
return this.config.type === 'GENERATED';
|
|
2060
|
+
}
|
|
2061
|
+
get isRow() {
|
|
2062
|
+
return this.config.type === 'ROW';
|
|
2063
|
+
}
|
|
2064
|
+
get isGroup() {
|
|
2065
|
+
return this.config.type === 'GROUP';
|
|
2066
|
+
}
|
|
2067
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, deps: [{ token: ExpressionService }, { token: i3.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
|
|
2068
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FormFieldComponent, isStandalone: false, selector: "lib-form-field", inputs: { config: "config", controller: "controller", sectionIndex: "sectionIndex" }, ngImport: i0, template: "<div class=\"form-field\" *ngIf=\"isVisible\" [class.has-error]=\"errorMessage\">\n <!-- ROW Layout -->\n <div *ngIf=\"isRow\" class=\"form-row\" [class.horizontal]=\"config.subType === 'HORIZONTAL'\">\n <ng-container *ngFor=\"let child of config.children\">\n <div class=\"row-field\">\n <lib-form-field\n [config]=\"child\"\n [controller]=\"controller\"\n [sectionIndex]=\"sectionIndex\">\n </lib-form-field>\n </div>\n </ng-container>\n </div>\n\n <!-- GROUP (Section) -->\n <div *ngIf=\"isGroup && config.sectionConfig\" class=\"form-group\">\n <ng-container *ngFor=\"let field of config.sectionConfig.children\">\n <lib-form-field\n [config]=\"field\"\n [controller]=\"controller\"\n [sectionIndex]=\"sectionIndex\">\n </lib-form-field>\n </ng-container>\n </div>\n\n <!-- Text Input -->\n <div *ngIf=\"isTextField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <textarea\n *ngIf=\"config.subType === 'LONG'\"\n class=\"field-input textarea\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (input)=\"onValueChange($event)\"\n rows=\"4\">\n </textarea>\n \n <input\n *ngIf=\"config.subType !== 'LONG'\"\n [type]=\"config.subType === 'EMAIL' ? 'email' : config.subType === 'PHONE' ? 'tel' : 'text'\"\n class=\"field-input\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Number Input -->\n <div *ngIf=\"isNumberField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <input\n type=\"number\"\n class=\"field-input\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n [min]=\"config.numberConfig?.min\"\n [max]=\"config.numberConfig?.max\"\n [step]=\"config.numberConfig?.step || 1\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Date Input -->\n <div *ngIf=\"isDateField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <input\n type=\"date\"\n class=\"field-input\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n [min]=\"config.dateConfig?.minDate\"\n [max]=\"config.dateConfig?.maxDate\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Dropdown -->\n <div *ngIf=\"isDropdown\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <select\n *ngIf=\"config.subType === 'SINGLE'\"\n class=\"field-input\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <option value=\"\">Select...</option>\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\n {{ option.label }}\n </option>\n </select>\n \n <select\n *ngIf=\"config.subType === 'MULTIPLE'\"\n class=\"field-input\"\n multiple\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\n {{ option.label }}\n </option>\n </select>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Radio -->\n <div *ngIf=\"isRadio\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"radio-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"radio-label\">\n <input\n type=\"radio\"\n [name]=\"getFieldName()\"\n [value]=\"option.code\"\n [checked]=\"value === option.code\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange(option.code)\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Checkbox -->\n <div *ngIf=\"isCheckbox\" class=\"field-wrapper\">\n <label *ngIf=\"config.label && config.subType === 'LIST'\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div *ngIf=\"config.subType === 'BOOL'\" class=\"checkbox-single\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n [checked]=\"value === true\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <span>{{ config.label }}</span>\n </label>\n </div>\n \n <div *ngIf=\"config.subType === 'LIST'\" class=\"checkbox-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n [checked]=\"isChecked(option.code)\"\n [disabled]=\"config.disabled\"\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Chip -->\n <div *ngIf=\"isChip\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"chip-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" \n class=\"chip-label\"\n [class.selected]=\"isChecked(option.code)\">\n <input\n type=\"checkbox\"\n [checked]=\"isChecked(option.code)\"\n [disabled]=\"config.disabled\"\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\"\n style=\"display: none;\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Switch -->\n <div *ngIf=\"isSwitch\" class=\"field-wrapper\">\n <label class=\"switch-container\">\n <span class=\"field-label\">{{ config.label }}</span>\n <div class=\"switch\">\n <input\n type=\"checkbox\"\n [checked]=\"value === true\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <span class=\"slider\"></span>\n </div>\n </label>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Rating -->\n <div *ngIf=\"isRating\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"rating-group\">\n <span *ngFor=\"let star of getStarArray()\" \n class=\"star\"\n [class.filled]=\"isStarFilled(star)\"\n [class.half]=\"isStarHalf(star)\"\n (click)=\"onRatingChange(star, $event)\">\n \u2605\n </span>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Generated Field (Read-only) -->\n <div *ngIf=\"isGenerated\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">{{ config.label }}</label>\n <div class=\"generated-value\">{{ value || '-' }}</div>\n <span class=\"field-hint\" *ngIf=\"config.hint\">{{ config.hint }}</span>\n </div>\n</div>\n", styles: [".form-field{margin-bottom:16px}.form-field.has-error .field-input{border-color:#f44336}.form-row{display:flex;gap:16px}.form-row.horizontal{flex-direction:row}.form-row.horizontal>*{flex:1}.form-row:not(.horizontal){flex-direction:column}.field-wrapper{display:flex;flex-direction:column;gap:6px}.field-label{font-size:14px;font-weight:500;color:#333}.field-label .required{color:#f44336;margin-left:4px}.field-input{padding:10px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px;transition:border-color .3s}.field-input:focus{outline:none;border-color:#2196f3}.field-input:disabled{background:#f5f5f5;cursor:not-allowed}.field-input.textarea{resize:vertical;font-family:inherit}.field-hint{font-size:12px;color:#666}.field-error{font-size:12px;color:#f44336}.radio-group,.checkbox-group{display:flex;flex-direction:column;gap:8px}.radio-label,.checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px}.radio-label input,.checkbox-label input{cursor:pointer}.checkbox-single .checkbox-label{font-weight:500}.chip-group{display:flex;flex-wrap:wrap;gap:8px}.chip-label{padding:8px 16px;border:1px solid #ddd;border-radius:20px;cursor:pointer;font-size:14px;transition:all .3s}.chip-label:hover{background:#f5f5f5}.chip-label.selected{background:#2196f3;color:#fff;border-color:#2196f3}.switch-container{display:flex;justify-content:space-between;align-items:center;cursor:pointer}.switch{position:relative;width:50px;height:24px}.switch input{opacity:0;width:0;height:0}.switch input:checked+.slider{background-color:#2196f3}.switch input:checked+.slider:before{transform:translate(26px)}.switch .slider{position:absolute;cursor:pointer;inset:0;background-color:#ccc;transition:.4s;border-radius:24px}.switch .slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:#fff;transition:.4s;border-radius:50%}.rating-group{display:flex;gap:4px}.rating-group .star{font-size:28px;display:inline-block;background:#ddd;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star.filled{background:#ffc107;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star.half{background:linear-gradient(90deg,#ffc107 50%,#ddd 50%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star:hover{background:#ffc107;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.generated-value{padding:10px 12px;background:#f5f5f5;border:1px solid #ddd;border-radius:4px;font-size:14px;color:#666}select[multiple]{min-height:120px}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "sectionIndex"] }] });
|
|
2069
|
+
}
|
|
2070
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormFieldComponent, decorators: [{
|
|
2071
|
+
type: Component,
|
|
2072
|
+
args: [{ selector: 'lib-form-field', standalone: false, template: "<div class=\"form-field\" *ngIf=\"isVisible\" [class.has-error]=\"errorMessage\">\n <!-- ROW Layout -->\n <div *ngIf=\"isRow\" class=\"form-row\" [class.horizontal]=\"config.subType === 'HORIZONTAL'\">\n <ng-container *ngFor=\"let child of config.children\">\n <div class=\"row-field\">\n <lib-form-field\n [config]=\"child\"\n [controller]=\"controller\"\n [sectionIndex]=\"sectionIndex\">\n </lib-form-field>\n </div>\n </ng-container>\n </div>\n\n <!-- GROUP (Section) -->\n <div *ngIf=\"isGroup && config.sectionConfig\" class=\"form-group\">\n <ng-container *ngFor=\"let field of config.sectionConfig.children\">\n <lib-form-field\n [config]=\"field\"\n [controller]=\"controller\"\n [sectionIndex]=\"sectionIndex\">\n </lib-form-field>\n </ng-container>\n </div>\n\n <!-- Text Input -->\n <div *ngIf=\"isTextField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <textarea\n *ngIf=\"config.subType === 'LONG'\"\n class=\"field-input textarea\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (input)=\"onValueChange($event)\"\n rows=\"4\">\n </textarea>\n \n <input\n *ngIf=\"config.subType !== 'LONG'\"\n [type]=\"config.subType === 'EMAIL' ? 'email' : config.subType === 'PHONE' ? 'tel' : 'text'\"\n class=\"field-input\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Number Input -->\n <div *ngIf=\"isNumberField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <input\n type=\"number\"\n class=\"field-input\"\n [placeholder]=\"config.hint || ''\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n [min]=\"config.numberConfig?.min\"\n [max]=\"config.numberConfig?.max\"\n [step]=\"config.numberConfig?.step || 1\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Date Input -->\n <div *ngIf=\"isDateField\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <input\n type=\"date\"\n class=\"field-input\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n [min]=\"config.dateConfig?.minDate\"\n [max]=\"config.dateConfig?.maxDate\"\n (input)=\"onValueChange($event)\">\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Dropdown -->\n <div *ngIf=\"isDropdown\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <select\n *ngIf=\"config.subType === 'SINGLE'\"\n class=\"field-input\"\n [value]=\"value || ''\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <option value=\"\">Select...</option>\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\n {{ option.label }}\n </option>\n </select>\n \n <select\n *ngIf=\"config.subType === 'MULTIPLE'\"\n class=\"field-input\"\n multiple\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <option *ngFor=\"let option of config.optionConfig?.optionList\" [value]=\"option.code\">\n {{ option.label }}\n </option>\n </select>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Radio -->\n <div *ngIf=\"isRadio\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"radio-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"radio-label\">\n <input\n type=\"radio\"\n [name]=\"getFieldName()\"\n [value]=\"option.code\"\n [checked]=\"value === option.code\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange(option.code)\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Checkbox -->\n <div *ngIf=\"isCheckbox\" class=\"field-wrapper\">\n <label *ngIf=\"config.label && config.subType === 'LIST'\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div *ngIf=\"config.subType === 'BOOL'\" class=\"checkbox-single\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n [checked]=\"value === true\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <span>{{ config.label }}</span>\n </label>\n </div>\n \n <div *ngIf=\"config.subType === 'LIST'\" class=\"checkbox-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n [checked]=\"isChecked(option.code)\"\n [disabled]=\"config.disabled\"\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Chip -->\n <div *ngIf=\"isChip\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"chip-group\">\n <label *ngFor=\"let option of config.optionConfig?.optionList\" \n class=\"chip-label\"\n [class.selected]=\"isChecked(option.code)\">\n <input\n type=\"checkbox\"\n [checked]=\"isChecked(option.code)\"\n [disabled]=\"config.disabled\"\n (change)=\"onCheckboxListChange(option.code, $any($event.target).checked)\"\n style=\"display: none;\">\n <span>{{ option.label }}</span>\n </label>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Switch -->\n <div *ngIf=\"isSwitch\" class=\"field-wrapper\">\n <label class=\"switch-container\">\n <span class=\"field-label\">{{ config.label }}</span>\n <div class=\"switch\">\n <input\n type=\"checkbox\"\n [checked]=\"value === true\"\n [disabled]=\"config.disabled\"\n (change)=\"onValueChange($event)\">\n <span class=\"slider\"></span>\n </div>\n </label>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Rating -->\n <div *ngIf=\"isRating\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">\n {{ config.label }}\n <span class=\"required\" *ngIf=\"config.required\">*</span>\n </label>\n \n <div class=\"rating-group\">\n <span *ngFor=\"let star of getStarArray()\" \n class=\"star\"\n [class.filled]=\"isStarFilled(star)\"\n [class.half]=\"isStarHalf(star)\"\n (click)=\"onRatingChange(star, $event)\">\n \u2605\n </span>\n </div>\n \n <span class=\"field-hint\" *ngIf=\"config.hint && !errorMessage\">{{ config.hint }}</span>\n <span class=\"field-error\" *ngIf=\"errorMessage\">{{ errorMessage }}</span>\n </div>\n\n <!-- Generated Field (Read-only) -->\n <div *ngIf=\"isGenerated\" class=\"field-wrapper\">\n <label *ngIf=\"config.label\" class=\"field-label\">{{ config.label }}</label>\n <div class=\"generated-value\">{{ value || '-' }}</div>\n <span class=\"field-hint\" *ngIf=\"config.hint\">{{ config.hint }}</span>\n </div>\n</div>\n", styles: [".form-field{margin-bottom:16px}.form-field.has-error .field-input{border-color:#f44336}.form-row{display:flex;gap:16px}.form-row.horizontal{flex-direction:row}.form-row.horizontal>*{flex:1}.form-row:not(.horizontal){flex-direction:column}.field-wrapper{display:flex;flex-direction:column;gap:6px}.field-label{font-size:14px;font-weight:500;color:#333}.field-label .required{color:#f44336;margin-left:4px}.field-input{padding:10px 12px;border:1px solid #ddd;border-radius:4px;font-size:14px;transition:border-color .3s}.field-input:focus{outline:none;border-color:#2196f3}.field-input:disabled{background:#f5f5f5;cursor:not-allowed}.field-input.textarea{resize:vertical;font-family:inherit}.field-hint{font-size:12px;color:#666}.field-error{font-size:12px;color:#f44336}.radio-group,.checkbox-group{display:flex;flex-direction:column;gap:8px}.radio-label,.checkbox-label{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px}.radio-label input,.checkbox-label input{cursor:pointer}.checkbox-single .checkbox-label{font-weight:500}.chip-group{display:flex;flex-wrap:wrap;gap:8px}.chip-label{padding:8px 16px;border:1px solid #ddd;border-radius:20px;cursor:pointer;font-size:14px;transition:all .3s}.chip-label:hover{background:#f5f5f5}.chip-label.selected{background:#2196f3;color:#fff;border-color:#2196f3}.switch-container{display:flex;justify-content:space-between;align-items:center;cursor:pointer}.switch{position:relative;width:50px;height:24px}.switch input{opacity:0;width:0;height:0}.switch input:checked+.slider{background-color:#2196f3}.switch input:checked+.slider:before{transform:translate(26px)}.switch .slider{position:absolute;cursor:pointer;inset:0;background-color:#ccc;transition:.4s;border-radius:24px}.switch .slider:before{position:absolute;content:\"\";height:18px;width:18px;left:3px;bottom:3px;background-color:#fff;transition:.4s;border-radius:50%}.rating-group{display:flex;gap:4px}.rating-group .star{font-size:28px;display:inline-block;background:#ddd;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star.filled{background:#ffc107;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star.half{background:linear-gradient(90deg,#ffc107 50%,#ddd 50%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.rating-group .star:hover{background:#ffc107;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.generated-value{padding:10px 12px;background:#f5f5f5;border:1px solid #ddd;border-radius:4px;font-size:14px;color:#666}select[multiple]{min-height:120px}\n"] }]
|
|
2073
|
+
}], ctorParameters: () => [{ type: ExpressionService }, { type: i3.HttpClient }], propDecorators: { config: [{
|
|
2074
|
+
type: Input
|
|
2075
|
+
}], controller: [{
|
|
2076
|
+
type: Input
|
|
2077
|
+
}], sectionIndex: [{
|
|
2078
|
+
type: Input
|
|
2079
|
+
}] } });
|
|
2080
|
+
|
|
2081
|
+
class FormSectionComponent {
|
|
2082
|
+
config;
|
|
2083
|
+
controller;
|
|
2084
|
+
sections = [{}];
|
|
2085
|
+
addSection() {
|
|
2086
|
+
if (this.config.allowMulti) {
|
|
2087
|
+
this.sections.push({});
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
removeSection(index) {
|
|
2091
|
+
if (this.sections.length > 1) {
|
|
2092
|
+
this.sections.splice(index, 1);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormSectionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2096
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FormSectionComponent, isStandalone: false, selector: "lib-form-section", inputs: { config: "config", controller: "controller" }, ngImport: i0, template: "<div class=\"form-section-container\">\n <h3 class=\"section-label\" *ngIf=\"config.label\">{{ config.label }}</h3>\n \n <div *ngFor=\"let section of sections; let sectionIndex = index\" class=\"section-instance\">\n <div class=\"section-header\" *ngIf=\"config.allowMulti && sections.length > 1\">\n <span class=\"section-number\">{{ config.label }} #{{ sectionIndex + 1 }}</span>\n <button \n type=\"button\" \n class=\"btn-remove\"\n (click)=\"removeSection(sectionIndex)\"\n *ngIf=\"sectionIndex > 0\">\n Remove\n </button>\n </div>\n \n <div class=\"section-fields\">\n <ng-container *ngFor=\"let field of config.children\">\n <lib-form-field\n [config]=\"field\"\n [controller]=\"controller\"\n [sectionIndex]=\"config.allowMulti ? sectionIndex : undefined\">\n </lib-form-field>\n </ng-container>\n </div>\n </div>\n \n <button \n type=\"button\" \n class=\"btn-add-section\"\n *ngIf=\"config.allowMulti\"\n (click)=\"addSection()\">\n + Add {{ config.label }}\n </button>\n</div>\n", styles: [".form-section-container{margin-bottom:24px}.form-section-container .section-label{font-size:18px;font-weight:600;color:#333;margin:0 0 16px}.form-section-container .section-instance{margin-bottom:16px;padding:16px;border:1px solid #e0e0e0;border-radius:4px}.form-section-container .section-instance:last-of-type{margin-bottom:0}.form-section-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e0e0e0}.form-section-container .section-header .section-number{font-weight:600;color:#333}.form-section-container .section-header .btn-remove{padding:6px 12px;background:#f44336;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px}.form-section-container .section-header .btn-remove:hover{background:#d32f2f}.form-section-container .section-fields{display:flex;flex-direction:column;gap:16px}.form-section-container .btn-add-section{padding:10px 20px;background:#2196f3;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;margin-top:16px}.form-section-container .btn-add-section:hover{background:#1976d2}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FormFieldComponent, selector: "lib-form-field", inputs: ["config", "controller", "sectionIndex"] }] });
|
|
2097
|
+
}
|
|
2098
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FormSectionComponent, decorators: [{
|
|
2099
|
+
type: Component,
|
|
2100
|
+
args: [{ selector: 'lib-form-section', standalone: false, template: "<div class=\"form-section-container\">\n <h3 class=\"section-label\" *ngIf=\"config.label\">{{ config.label }}</h3>\n \n <div *ngFor=\"let section of sections; let sectionIndex = index\" class=\"section-instance\">\n <div class=\"section-header\" *ngIf=\"config.allowMulti && sections.length > 1\">\n <span class=\"section-number\">{{ config.label }} #{{ sectionIndex + 1 }}</span>\n <button \n type=\"button\" \n class=\"btn-remove\"\n (click)=\"removeSection(sectionIndex)\"\n *ngIf=\"sectionIndex > 0\">\n Remove\n </button>\n </div>\n \n <div class=\"section-fields\">\n <ng-container *ngFor=\"let field of config.children\">\n <lib-form-field\n [config]=\"field\"\n [controller]=\"controller\"\n [sectionIndex]=\"config.allowMulti ? sectionIndex : undefined\">\n </lib-form-field>\n </ng-container>\n </div>\n </div>\n \n <button \n type=\"button\" \n class=\"btn-add-section\"\n *ngIf=\"config.allowMulti\"\n (click)=\"addSection()\">\n + Add {{ config.label }}\n </button>\n</div>\n", styles: [".form-section-container{margin-bottom:24px}.form-section-container .section-label{font-size:18px;font-weight:600;color:#333;margin:0 0 16px}.form-section-container .section-instance{margin-bottom:16px;padding:16px;border:1px solid #e0e0e0;border-radius:4px}.form-section-container .section-instance:last-of-type{margin-bottom:0}.form-section-container .section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid #e0e0e0}.form-section-container .section-header .section-number{font-weight:600;color:#333}.form-section-container .section-header .btn-remove{padding:6px 12px;background:#f44336;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px}.form-section-container .section-header .btn-remove:hover{background:#d32f2f}.form-section-container .section-fields{display:flex;flex-direction:column;gap:16px}.form-section-container .btn-add-section{padding:10px 20px;background:#2196f3;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;margin-top:16px}.form-section-container .btn-add-section:hover{background:#1976d2}\n"] }]
|
|
2101
|
+
}], propDecorators: { config: [{
|
|
2102
|
+
type: Input
|
|
2103
|
+
}], controller: [{
|
|
2104
|
+
type: Input
|
|
2105
|
+
}] } });
|
|
2106
|
+
|
|
2107
|
+
class SmartFormComponent {
|
|
2108
|
+
fb;
|
|
2109
|
+
controller;
|
|
2110
|
+
expressionService;
|
|
2111
|
+
http;
|
|
2112
|
+
formJson;
|
|
2113
|
+
initialValues;
|
|
2114
|
+
enableDraftAutoSave = false;
|
|
2115
|
+
submit = new EventEmitter();
|
|
2116
|
+
draftSave = new EventEmitter();
|
|
2117
|
+
formSchema;
|
|
2118
|
+
formGroup;
|
|
2119
|
+
fieldList = [];
|
|
2120
|
+
isStepper = false;
|
|
2121
|
+
currentStep = 0;
|
|
2122
|
+
isLoading = false;
|
|
2123
|
+
constructor(fb, controller, expressionService, http) {
|
|
2124
|
+
this.fb = fb;
|
|
2125
|
+
this.controller = controller;
|
|
2126
|
+
this.expressionService = expressionService;
|
|
2127
|
+
this.http = http;
|
|
2128
|
+
}
|
|
2129
|
+
ngOnInit() {
|
|
2130
|
+
if (this.initialValues) {
|
|
2131
|
+
this.controller.initialize(this.initialValues);
|
|
2132
|
+
}
|
|
2133
|
+
this.parseFormJson();
|
|
2134
|
+
}
|
|
2135
|
+
ngOnChanges(changes) {
|
|
2136
|
+
if (changes['formJson'] && !changes['formJson'].isFirstChange()) {
|
|
2137
|
+
this.parseFormJson();
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
ngOnDestroy() {
|
|
2141
|
+
this.controller.destroy();
|
|
2142
|
+
}
|
|
2143
|
+
parseFormJson() {
|
|
2144
|
+
try {
|
|
2145
|
+
const jsonData = JSON.parse(this.formJson);
|
|
2146
|
+
this.formSchema = jsonData;
|
|
2147
|
+
this.isStepper = this.formSchema.formType === 'STEPPER';
|
|
2148
|
+
this.fieldList = this.isStepper
|
|
2149
|
+
? this.formSchema.stepperConfig?.children || []
|
|
2150
|
+
: this.formSchema.sectionConfig?.children || [];
|
|
2151
|
+
this.initializeForm();
|
|
2152
|
+
}
|
|
2153
|
+
catch (e) {
|
|
2154
|
+
console.error('Error parsing form JSON:', e);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
initializeForm() {
|
|
2158
|
+
this.formGroup = this.fb.group({});
|
|
2159
|
+
this.collectFields(this.fieldList);
|
|
2160
|
+
}
|
|
2161
|
+
collectFields(fields) {
|
|
2162
|
+
fields.forEach(field => {
|
|
2163
|
+
if (field.name) {
|
|
2164
|
+
const value = field.defaultValue !== undefined ? field.defaultValue : null;
|
|
2165
|
+
this.controller.updateField(field.name, value);
|
|
2166
|
+
}
|
|
2167
|
+
if (field.children && field.children.length > 0) {
|
|
2168
|
+
this.collectFields(field.children);
|
|
2169
|
+
}
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
handleSubmit() {
|
|
2173
|
+
if (this.isStepper && this.currentStep < this.fieldList.length - 1) {
|
|
2174
|
+
this.nextStep();
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
const formData = this.controller.getAllData();
|
|
2178
|
+
this.isLoading = true;
|
|
2179
|
+
if (this.formSchema.submitConfig?.apiUrl) {
|
|
2180
|
+
this.submitToApi(formData);
|
|
2181
|
+
}
|
|
2182
|
+
else {
|
|
2183
|
+
this.submit.emit(formData);
|
|
2184
|
+
this.isLoading = false;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
submitToApi(formData) {
|
|
2188
|
+
const config = this.formSchema.submitConfig;
|
|
2189
|
+
const method = config.method || 'POST';
|
|
2190
|
+
this.http.request(method, config.apiUrl, { body: formData })
|
|
2191
|
+
.subscribe({
|
|
2192
|
+
next: (response) => {
|
|
2193
|
+
alert(config.successMessage || 'Form submitted successfully');
|
|
2194
|
+
this.submit.emit(response);
|
|
2195
|
+
this.isLoading = false;
|
|
2196
|
+
},
|
|
2197
|
+
error: (err) => {
|
|
2198
|
+
alert(config.errorMessage || 'Failed to submit form');
|
|
2199
|
+
console.error('Submit error:', err);
|
|
2200
|
+
this.isLoading = false;
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
nextStep() {
|
|
2205
|
+
if (this.currentStep < this.fieldList.length - 1) {
|
|
2206
|
+
this.currentStep++;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
previousStep() {
|
|
2210
|
+
if (this.currentStep > 0) {
|
|
2211
|
+
this.currentStep--;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
get canGoNext() {
|
|
2215
|
+
return this.currentStep < this.fieldList.length - 1;
|
|
2216
|
+
}
|
|
2217
|
+
get canGoPrevious() {
|
|
2218
|
+
return this.currentStep > 0;
|
|
2219
|
+
}
|
|
2220
|
+
get currentStepConfig() {
|
|
2221
|
+
return this.isStepper ? this.fieldList[this.currentStep] : undefined;
|
|
2222
|
+
}
|
|
2223
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormComponent, deps: [{ token: i1$2.FormBuilder }, { token: SmartFormController }, { token: ExpressionService }, { token: i3.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
|
|
2224
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SmartFormComponent, isStandalone: false, selector: "lib-smart-form", inputs: { formJson: "formJson", initialValues: "initialValues", enableDraftAutoSave: "enableDraftAutoSave" }, outputs: { submit: "submit", draftSave: "draftSave" }, providers: [SmartFormController], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-form-container\">\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema\">\n <!-- Form Header -->\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\n </div>\n\n <!-- Stepper Navigation -->\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\n <div \n *ngFor=\"let step of fieldList; let i = index\"\n class=\"stepper-step\"\n [class.active]=\"i === currentStep\"\n [class.completed]=\"i < currentStep\">\n <div class=\"step-number\">{{ i + 1 }}</div>\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\n </div>\n </div>\n </div>\n\n <!-- Form Content -->\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\n <!-- Section Form -->\n <div *ngIf=\"!isStepper && formSchema.sectionConfig\" class=\"form-section\">\n <lib-form-section \n [config]=\"formSchema.sectionConfig\"\n [controller]=\"controller\">\n </lib-form-section>\n </div>\n\n <!-- Stepper Form -->\n <div *ngIf=\"isStepper && currentStepConfig\" class=\"form-stepper\">\n <lib-form-section \n [config]=\"currentStepConfig.sectionConfig!\"\n [controller]=\"controller\">\n </lib-form-section>\n </div>\n </form>\n\n <!-- Form Actions -->\n <div class=\"form-actions\">\n <button \n *ngIf=\"isStepper && canGoPrevious\"\n type=\"button\" \n class=\"btn btn-secondary\"\n (click)=\"previousStep()\">\n Previous\n </button>\n \n <button \n type=\"button\" \n class=\"btn btn-primary\"\n [disabled]=\"isLoading\"\n (click)=\"handleSubmit()\">\n {{ isStepper && canGoNext ? 'Next' : 'Submit' }}\n </button>\n </div>\n </div>\n</div>\n", styles: [".smart-form-container{width:100%;max-width:1200px;margin:0 auto;padding:20px}.smart-form-wrapper{background:#fff;border-radius:8px;box-shadow:0 2px 8px #0000001a;padding:24px}.form-header{margin-bottom:24px}.form-header .form-title{font-size:24px;font-weight:600;color:#333;margin:0 0 8px}.form-header .form-description{font-size:14px;color:#666;margin:0}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:20px;left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:#e0e0e0}.stepper-nav .stepper-step.completed:after{background:#4caf50}.stepper-nav .stepper-step .step-number{width:40px;height:40px;border-radius:50%;background:#e0e0e0;color:#666;display:flex;align-items:center;justify-content:center;font-weight:600;transition:all .3s}.stepper-nav .stepper-step .step-label{font-size:14px;color:#666;font-weight:500}.stepper-nav .stepper-step.active .step-number{background:#2196f3;color:#fff}.stepper-nav .stepper-step.active .step-label{color:#2196f3;font-weight:600}.stepper-nav .stepper-step.completed .step-number{background:#4caf50;color:#fff}.smart-form{margin-bottom:24px}.form-actions{display:flex;justify-content:flex-end;gap:12px;padding-top:24px;border-top:1px solid #e0e0e0}.form-actions .btn{padding:10px 24px;border-radius:4px;font-size:14px;font-weight:500;border:none;cursor:pointer;transition:all .3s}.form-actions .btn.btn-primary{background:#2196f3;color:#fff}.form-actions .btn.btn-primary:hover:not(:disabled){background:#1976d2}.form-actions .btn.btn-primary:disabled{opacity:.6;cursor:not-allowed}.form-actions .btn.btn-secondary{background:#f5f5f5;color:#333}.form-actions .btn.btn-secondary:hover{background:#e0e0e0}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FormSectionComponent, selector: "lib-form-section", inputs: ["config", "controller"] }] });
|
|
2225
|
+
}
|
|
2226
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormComponent, decorators: [{
|
|
2227
|
+
type: Component,
|
|
2228
|
+
args: [{ selector: 'lib-smart-form', providers: [SmartFormController], standalone: false, template: "<div class=\"smart-form-container\">\n <div class=\"smart-form-wrapper\" *ngIf=\"formSchema\">\n <!-- Form Header -->\n <div class=\"form-header\" *ngIf=\"formSchema.showTitle !== false\">\n <h2 class=\"form-title\">{{ formSchema.label }}</h2>\n <p class=\"form-description\" *ngIf=\"formSchema.description\">{{ formSchema.description }}</p>\n </div>\n\n <!-- Stepper Navigation -->\n <div class=\"stepper-nav\" *ngIf=\"isStepper && formSchema.stepperConfig?.showStep !== false\">\n <div class=\"stepper-steps\" [class.horizontal]=\"formSchema.stepperConfig?.isHorizontal !== false\">\n <div \n *ngFor=\"let step of fieldList; let i = index\"\n class=\"stepper-step\"\n [class.active]=\"i === currentStep\"\n [class.completed]=\"i < currentStep\">\n <div class=\"step-number\">{{ i + 1 }}</div>\n <div class=\"step-label\">{{ step.sectionConfig?.label || 'Step ' + (i + 1) }}</div>\n </div>\n </div>\n </div>\n\n <!-- Form Content -->\n <form [formGroup]=\"formGroup\" class=\"smart-form\">\n <!-- Section Form -->\n <div *ngIf=\"!isStepper && formSchema.sectionConfig\" class=\"form-section\">\n <lib-form-section \n [config]=\"formSchema.sectionConfig\"\n [controller]=\"controller\">\n </lib-form-section>\n </div>\n\n <!-- Stepper Form -->\n <div *ngIf=\"isStepper && currentStepConfig\" class=\"form-stepper\">\n <lib-form-section \n [config]=\"currentStepConfig.sectionConfig!\"\n [controller]=\"controller\">\n </lib-form-section>\n </div>\n </form>\n\n <!-- Form Actions -->\n <div class=\"form-actions\">\n <button \n *ngIf=\"isStepper && canGoPrevious\"\n type=\"button\" \n class=\"btn btn-secondary\"\n (click)=\"previousStep()\">\n Previous\n </button>\n \n <button \n type=\"button\" \n class=\"btn btn-primary\"\n [disabled]=\"isLoading\"\n (click)=\"handleSubmit()\">\n {{ isStepper && canGoNext ? 'Next' : 'Submit' }}\n </button>\n </div>\n </div>\n</div>\n", styles: [".smart-form-container{width:100%;max-width:1200px;margin:0 auto;padding:20px}.smart-form-wrapper{background:#fff;border-radius:8px;box-shadow:0 2px 8px #0000001a;padding:24px}.form-header{margin-bottom:24px}.form-header .form-title{font-size:24px;font-weight:600;color:#333;margin:0 0 8px}.form-header .form-description{font-size:14px;color:#666;margin:0}.stepper-nav{margin-bottom:32px}.stepper-nav .stepper-steps{display:flex;gap:16px}.stepper-nav .stepper-steps.horizontal{flex-direction:row;justify-content:space-between}.stepper-nav .stepper-steps:not(.horizontal){flex-direction:column}.stepper-nav .stepper-step{display:flex;align-items:center;gap:12px;flex:1;position:relative}.stepper-nav .stepper-step:not(:last-child):after{content:\"\";position:absolute;top:20px;left:calc(100% + 8px);width:calc(100% - 40px);height:2px;background:#e0e0e0}.stepper-nav .stepper-step.completed:after{background:#4caf50}.stepper-nav .stepper-step .step-number{width:40px;height:40px;border-radius:50%;background:#e0e0e0;color:#666;display:flex;align-items:center;justify-content:center;font-weight:600;transition:all .3s}.stepper-nav .stepper-step .step-label{font-size:14px;color:#666;font-weight:500}.stepper-nav .stepper-step.active .step-number{background:#2196f3;color:#fff}.stepper-nav .stepper-step.active .step-label{color:#2196f3;font-weight:600}.stepper-nav .stepper-step.completed .step-number{background:#4caf50;color:#fff}.smart-form{margin-bottom:24px}.form-actions{display:flex;justify-content:flex-end;gap:12px;padding-top:24px;border-top:1px solid #e0e0e0}.form-actions .btn{padding:10px 24px;border-radius:4px;font-size:14px;font-weight:500;border:none;cursor:pointer;transition:all .3s}.form-actions .btn.btn-primary{background:#2196f3;color:#fff}.form-actions .btn.btn-primary:hover:not(:disabled){background:#1976d2}.form-actions .btn.btn-primary:disabled{opacity:.6;cursor:not-allowed}.form-actions .btn.btn-secondary{background:#f5f5f5;color:#333}.form-actions .btn.btn-secondary:hover{background:#e0e0e0}\n"] }]
|
|
2229
|
+
}], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: SmartFormController }, { type: ExpressionService }, { type: i3.HttpClient }], propDecorators: { formJson: [{
|
|
2230
|
+
type: Input
|
|
2231
|
+
}], initialValues: [{
|
|
2232
|
+
type: Input
|
|
2233
|
+
}], enableDraftAutoSave: [{
|
|
2234
|
+
type: Input
|
|
2235
|
+
}], submit: [{
|
|
2236
|
+
type: Output
|
|
2237
|
+
}], draftSave: [{
|
|
2238
|
+
type: Output
|
|
2239
|
+
}] } });
|
|
2240
|
+
|
|
2241
|
+
class SmartFormModule {
|
|
2242
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2243
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, declarations: [SmartFormComponent,
|
|
2244
|
+
FormSectionComponent,
|
|
2245
|
+
FormFieldComponent], imports: [CommonModule,
|
|
2246
|
+
ReactiveFormsModule,
|
|
2247
|
+
FormsModule], exports: [SmartFormComponent] });
|
|
2248
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, providers: [
|
|
2249
|
+
ExpressionService
|
|
2250
|
+
], imports: [CommonModule,
|
|
2251
|
+
ReactiveFormsModule,
|
|
2252
|
+
FormsModule] });
|
|
2253
|
+
}
|
|
2254
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartFormModule, decorators: [{
|
|
2255
|
+
type: NgModule,
|
|
2256
|
+
args: [{
|
|
2257
|
+
declarations: [
|
|
2258
|
+
SmartFormComponent,
|
|
2259
|
+
FormSectionComponent,
|
|
2260
|
+
FormFieldComponent
|
|
2261
|
+
],
|
|
2262
|
+
imports: [
|
|
2263
|
+
CommonModule,
|
|
2264
|
+
ReactiveFormsModule,
|
|
2265
|
+
FormsModule
|
|
2266
|
+
],
|
|
2267
|
+
exports: [
|
|
2268
|
+
SmartFormComponent
|
|
2269
|
+
],
|
|
2270
|
+
providers: [
|
|
2271
|
+
ExpressionService
|
|
2272
|
+
]
|
|
2273
|
+
}]
|
|
2274
|
+
}] });
|
|
2275
|
+
|
|
1582
2276
|
class SharedUiModule {
|
|
1583
2277
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SharedUiModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1584
2278
|
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SharedUiModule, imports: [CommonModule,
|
|
1585
2279
|
MaterialModule,
|
|
1586
|
-
|
|
2280
|
+
AlertModule, ButtonModule, ConfirmationModalModule, FilterSidebarModule,
|
|
1587
2281
|
SummaryCardModule,
|
|
1588
|
-
ConfigurableFormModule
|
|
1589
|
-
|
|
2282
|
+
ConfigurableFormModule,
|
|
2283
|
+
SmartFormModule], exports: [MaterialModule,
|
|
2284
|
+
AlertModule, ButtonModule, ConfirmationModalModule, FilterSidebarModule,
|
|
1590
2285
|
SummaryCardModule,
|
|
1591
|
-
ConfigurableFormModule
|
|
2286
|
+
ConfigurableFormModule,
|
|
2287
|
+
SmartFormModule] });
|
|
1592
2288
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SharedUiModule, imports: [CommonModule,
|
|
1593
2289
|
MaterialModule,
|
|
1594
|
-
|
|
2290
|
+
AlertModule, ButtonModule, ConfirmationModalModule, FilterSidebarModule,
|
|
1595
2291
|
SummaryCardModule,
|
|
1596
|
-
ConfigurableFormModule,
|
|
1597
|
-
|
|
2292
|
+
ConfigurableFormModule,
|
|
2293
|
+
SmartFormModule, MaterialModule,
|
|
2294
|
+
AlertModule, ButtonModule, ConfirmationModalModule, FilterSidebarModule,
|
|
1598
2295
|
SummaryCardModule,
|
|
1599
|
-
ConfigurableFormModule
|
|
2296
|
+
ConfigurableFormModule,
|
|
2297
|
+
SmartFormModule] });
|
|
1600
2298
|
}
|
|
1601
2299
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SharedUiModule, decorators: [{
|
|
1602
2300
|
type: NgModule,
|
|
@@ -1605,15 +2303,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
1605
2303
|
imports: [
|
|
1606
2304
|
CommonModule,
|
|
1607
2305
|
MaterialModule,
|
|
1608
|
-
|
|
2306
|
+
AlertModule, ButtonModule, ConfirmationModalModule, FilterSidebarModule,
|
|
1609
2307
|
SummaryCardModule,
|
|
1610
2308
|
ConfigurableFormModule,
|
|
2309
|
+
SmartFormModule,
|
|
1611
2310
|
],
|
|
1612
2311
|
exports: [
|
|
1613
2312
|
MaterialModule,
|
|
1614
|
-
|
|
2313
|
+
AlertModule, ButtonModule, ConfirmationModalModule, FilterSidebarModule,
|
|
1615
2314
|
SummaryCardModule,
|
|
1616
2315
|
ConfigurableFormModule,
|
|
2316
|
+
SmartFormModule,
|
|
1617
2317
|
],
|
|
1618
2318
|
}]
|
|
1619
2319
|
}] });
|
|
@@ -2033,6 +2733,900 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
2033
2733
|
}]
|
|
2034
2734
|
}] });
|
|
2035
2735
|
|
|
2736
|
+
class SmartTableComponent {
|
|
2737
|
+
http;
|
|
2738
|
+
router;
|
|
2739
|
+
cdr;
|
|
2740
|
+
ngZone;
|
|
2741
|
+
config;
|
|
2742
|
+
action = new EventEmitter();
|
|
2743
|
+
topAction = new EventEmitter(); // For top bar buttons
|
|
2744
|
+
filterChange = new EventEmitter();
|
|
2745
|
+
rowSelect = new EventEmitter();
|
|
2746
|
+
rowClick = new EventEmitter();
|
|
2747
|
+
data = [];
|
|
2748
|
+
totalItems = 0;
|
|
2749
|
+
currentPage = 1;
|
|
2750
|
+
loading = false;
|
|
2751
|
+
// State
|
|
2752
|
+
activeSort = { key: '', direction: 'ASC' };
|
|
2753
|
+
activeFilters = {};
|
|
2754
|
+
searchTerm = '';
|
|
2755
|
+
selectedRows = [];
|
|
2756
|
+
stickyColumnStyles = {};
|
|
2757
|
+
hasStickyColumns = false;
|
|
2758
|
+
searchSubject = new Subject();
|
|
2759
|
+
stickyHeaders;
|
|
2760
|
+
resizeObserver = null;
|
|
2761
|
+
locale = inject(LOCALE_ID);
|
|
2762
|
+
constructor(http, router, cdr, ngZone) {
|
|
2763
|
+
this.http = http;
|
|
2764
|
+
this.router = router;
|
|
2765
|
+
this.cdr = cdr;
|
|
2766
|
+
this.ngZone = ngZone;
|
|
2767
|
+
// Debounce search input
|
|
2768
|
+
this.searchSubject.pipe(debounceTime(this.config?.searchConfig?.debounceTime || 300), distinctUntilChanged()).subscribe(term => {
|
|
2769
|
+
this.searchTerm = term;
|
|
2770
|
+
this.currentPage = 1;
|
|
2771
|
+
this.loadData();
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
ngOnInit() {
|
|
2775
|
+
if (this.config) {
|
|
2776
|
+
if (this.config.sortBy) {
|
|
2777
|
+
this.activeSort.key = this.config.sortBy;
|
|
2778
|
+
}
|
|
2779
|
+
if (this.config.orderBy) {
|
|
2780
|
+
this.activeSort.direction = this.config.orderBy;
|
|
2781
|
+
}
|
|
2782
|
+
this.loadFilterOptions();
|
|
2783
|
+
this.loadData();
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
ngOnChanges(changes) {
|
|
2787
|
+
if (changes['config'] && !changes['config'].firstChange) {
|
|
2788
|
+
this.loadFilterOptions();
|
|
2789
|
+
this.loadData();
|
|
2790
|
+
setTimeout(() => this.calculateStickyPositions());
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
ngAfterViewInit() {
|
|
2794
|
+
this.setupResizeObserver();
|
|
2795
|
+
}
|
|
2796
|
+
ngOnDestroy() {
|
|
2797
|
+
if (this.resizeObserver) {
|
|
2798
|
+
this.resizeObserver.disconnect();
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
setupResizeObserver() {
|
|
2802
|
+
if (this.resizeObserver) {
|
|
2803
|
+
this.resizeObserver.disconnect();
|
|
2804
|
+
}
|
|
2805
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
2806
|
+
this.ngZone.run(() => {
|
|
2807
|
+
this.calculateStickyPositions();
|
|
2808
|
+
});
|
|
2809
|
+
});
|
|
2810
|
+
if (this.stickyHeaders) {
|
|
2811
|
+
this.stickyHeaders.changes.subscribe(() => {
|
|
2812
|
+
this.observeHeaders();
|
|
2813
|
+
// Also recalculate immediately
|
|
2814
|
+
setTimeout(() => this.calculateStickyPositions());
|
|
2815
|
+
});
|
|
2816
|
+
this.observeHeaders();
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
observeHeaders() {
|
|
2820
|
+
if (!this.resizeObserver || !this.stickyHeaders)
|
|
2821
|
+
return;
|
|
2822
|
+
this.stickyHeaders.forEach(header => {
|
|
2823
|
+
this.resizeObserver.observe(header.nativeElement);
|
|
2824
|
+
});
|
|
2825
|
+
}
|
|
2826
|
+
loadData() {
|
|
2827
|
+
if (!this.config?.apiUrl)
|
|
2828
|
+
return;
|
|
2829
|
+
this.loading = true;
|
|
2830
|
+
let params;
|
|
2831
|
+
// --- Query Params Construction ---
|
|
2832
|
+
if (this.config.queryParamsConfig) {
|
|
2833
|
+
const qpConfig = this.config.queryParamsConfig;
|
|
2834
|
+
const pageKey = qpConfig.pageKey || 'page';
|
|
2835
|
+
const sizeKey = qpConfig.sizeKey || 'pageSize';
|
|
2836
|
+
const pageIndex = this.currentPage + (qpConfig.pageIndexOffset || 0);
|
|
2837
|
+
let paramsObj = {
|
|
2838
|
+
[pageKey]: pageIndex.toString(),
|
|
2839
|
+
[sizeKey]: (this.config.pagination?.pageSize || 10).toString()
|
|
2840
|
+
};
|
|
2841
|
+
if (this.activeSort.key)
|
|
2842
|
+
paramsObj['sortBy'] = this.activeSort.key;
|
|
2843
|
+
// Note: Some APIs might use different keys for sort/order, can be extended later if needed
|
|
2844
|
+
if (this.activeSort.direction)
|
|
2845
|
+
paramsObj['orderBy'] = this.activeSort.direction;
|
|
2846
|
+
// Search Handling
|
|
2847
|
+
if (this.searchTerm) {
|
|
2848
|
+
const searchConfig = this.config.searchConfig;
|
|
2849
|
+
const searchKey = searchConfig?.searchKey || 'search';
|
|
2850
|
+
if (searchConfig?.handling === 'nested_string' && qpConfig.nestedStringConfig) {
|
|
2851
|
+
// Will be handled in nested string construction below
|
|
2852
|
+
}
|
|
2853
|
+
else {
|
|
2854
|
+
paramsObj[searchKey] = this.searchTerm;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
// Filter Handling (and Search if nested)
|
|
2858
|
+
if (qpConfig.filterHandling === 'nested_string' && qpConfig.nestedStringConfig) {
|
|
2859
|
+
const { paramName, baseValue, separator, assignment } = qpConfig.nestedStringConfig;
|
|
2860
|
+
let nestedString = baseValue || '';
|
|
2861
|
+
const assign = assignment || '=';
|
|
2862
|
+
// Add Filters
|
|
2863
|
+
Object.keys(this.activeFilters).forEach(key => {
|
|
2864
|
+
if (this.activeFilters[key]) {
|
|
2865
|
+
const prefix = nestedString ? separator : '';
|
|
2866
|
+
nestedString += `${prefix}${key}${assign}${this.activeFilters[key]}`;
|
|
2867
|
+
}
|
|
2868
|
+
});
|
|
2869
|
+
// Add Search if nested
|
|
2870
|
+
if (this.searchTerm && this.config.searchConfig?.handling === 'nested_string') {
|
|
2871
|
+
const searchKey = this.config.searchConfig.searchKey || 'SEARCH_TERM';
|
|
2872
|
+
const prefix = nestedString ? separator : '';
|
|
2873
|
+
nestedString += `${prefix}${searchKey}${assign}${this.searchTerm}`;
|
|
2874
|
+
}
|
|
2875
|
+
paramsObj[paramName] = nestedString;
|
|
2876
|
+
}
|
|
2877
|
+
else {
|
|
2878
|
+
// Standard handling
|
|
2879
|
+
Object.keys(this.activeFilters).forEach(key => {
|
|
2880
|
+
if (this.activeFilters[key])
|
|
2881
|
+
paramsObj[key] = this.activeFilters[key];
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
params = new HttpParams({ fromObject: paramsObj });
|
|
2885
|
+
}
|
|
2886
|
+
else {
|
|
2887
|
+
// --- Default Behavior (Backward Compatibility) ---
|
|
2888
|
+
let paramsObj = {};
|
|
2889
|
+
if (this.config.pagination?.enabled) {
|
|
2890
|
+
paramsObj['page'] = this.currentPage;
|
|
2891
|
+
paramsObj['pageSize'] = this.config.pagination.pageSize;
|
|
2892
|
+
}
|
|
2893
|
+
if (this.activeSort.key) {
|
|
2894
|
+
paramsObj['sortBy'] = this.activeSort.key;
|
|
2895
|
+
paramsObj['orderBy'] = this.activeSort.direction;
|
|
2896
|
+
}
|
|
2897
|
+
if (this.searchTerm) {
|
|
2898
|
+
const searchKey = this.config.searchConfig?.searchKey || 'search';
|
|
2899
|
+
paramsObj[searchKey] = this.searchTerm;
|
|
2900
|
+
}
|
|
2901
|
+
Object.keys(this.activeFilters).forEach(key => {
|
|
2902
|
+
if (this.activeFilters[key]) {
|
|
2903
|
+
paramsObj[key] = this.activeFilters[key];
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
// Custom Request Params Mapper (if provided)
|
|
2907
|
+
if (this.config.requestParams) {
|
|
2908
|
+
const customParams = this.config.requestParams(this.currentPage, this.config.pagination?.pageSize || 10, this.activeSort.key, this.activeSort.direction, this.searchTerm, this.activeFilters);
|
|
2909
|
+
if (customParams instanceof HttpParams) {
|
|
2910
|
+
// If function returns HttpParams, use it directly (caveat: might lose previous params if not careful in mapper)
|
|
2911
|
+
params = customParams;
|
|
2912
|
+
}
|
|
2913
|
+
else {
|
|
2914
|
+
paramsObj = { ...paramsObj, ...customParams };
|
|
2915
|
+
params = new HttpParams({ fromObject: paramsObj });
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
else {
|
|
2919
|
+
params = new HttpParams({ fromObject: paramsObj });
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
// --- Data Fetching ---
|
|
2923
|
+
// Check for separate count API
|
|
2924
|
+
const totalCountConfig = this.config.pagination?.totalCountConfig;
|
|
2925
|
+
let request$; // Observable
|
|
2926
|
+
if (totalCountConfig?.source === 'separate' && totalCountConfig.apiUrl) {
|
|
2927
|
+
request$ = forkJoin({
|
|
2928
|
+
data: this.http.get(this.config.apiUrl, { params }),
|
|
2929
|
+
count: this.http.get(totalCountConfig.apiUrl, { params })
|
|
2930
|
+
}).pipe(map(({ data, count }) => {
|
|
2931
|
+
const dataPath = this.config.dataResponsePath !== undefined ? this.config.dataResponsePath : '';
|
|
2932
|
+
return {
|
|
2933
|
+
data: this.getValueByPath(data, dataPath),
|
|
2934
|
+
total: this.getValueByPath(count, totalCountConfig.responsePath || '')
|
|
2935
|
+
};
|
|
2936
|
+
}));
|
|
2937
|
+
}
|
|
2938
|
+
else {
|
|
2939
|
+
request$ = this.http.get(this.config.apiUrl, { params }).pipe(map(response => {
|
|
2940
|
+
const dataPath = this.config.dataResponsePath !== undefined ? this.config.dataResponsePath : '';
|
|
2941
|
+
const totalPath = totalCountConfig?.responsePath || '';
|
|
2942
|
+
return {
|
|
2943
|
+
data: this.getValueByPath(response, dataPath),
|
|
2944
|
+
// If source is 'same', try to get total from response, else default 0
|
|
2945
|
+
total: totalCountConfig ? this.getValueByPath(response, totalPath) : 0
|
|
2946
|
+
};
|
|
2947
|
+
}));
|
|
2948
|
+
}
|
|
2949
|
+
request$.pipe(finalize(() => this.loading = false), catchError(err => {
|
|
2950
|
+
console.error('Table Data Fetch Error', err);
|
|
2951
|
+
return of({ data: [], total: 0 });
|
|
2952
|
+
}))
|
|
2953
|
+
.subscribe((result) => {
|
|
2954
|
+
this.data = result.data || [];
|
|
2955
|
+
if (this.config.pagination) {
|
|
2956
|
+
this.totalItems = result.total || 0;
|
|
2957
|
+
}
|
|
2958
|
+
// Dynamic Column Generation
|
|
2959
|
+
if (!this.config.columns || this.config.columns.length === 0) {
|
|
2960
|
+
if (this.data.length > 0) {
|
|
2961
|
+
this.config.columns = Object.keys(this.data[0]).map(key => ({
|
|
2962
|
+
key: key,
|
|
2963
|
+
label: this.toTitleCase(key),
|
|
2964
|
+
type: 'text',
|
|
2965
|
+
sortable: false,
|
|
2966
|
+
editable: false
|
|
2967
|
+
}));
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
});
|
|
2971
|
+
}
|
|
2972
|
+
// --- Actions ---
|
|
2973
|
+
onPageChange(page) {
|
|
2974
|
+
this.currentPage = page;
|
|
2975
|
+
this.loadData();
|
|
2976
|
+
}
|
|
2977
|
+
onPageSizeChange(size) {
|
|
2978
|
+
if (this.config.pagination) {
|
|
2979
|
+
this.config.pagination.pageSize = size;
|
|
2980
|
+
this.currentPage = 1; // Reset to first page
|
|
2981
|
+
this.loadData();
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
onSort(col) {
|
|
2985
|
+
if (!col.sortable)
|
|
2986
|
+
return;
|
|
2987
|
+
if (this.activeSort.key === col.key) {
|
|
2988
|
+
this.activeSort.direction = this.activeSort.direction === 'ASC' ? 'DESC' : 'ASC';
|
|
2989
|
+
}
|
|
2990
|
+
else {
|
|
2991
|
+
this.activeSort.key = col.key;
|
|
2992
|
+
this.activeSort.direction = 'ASC';
|
|
2993
|
+
}
|
|
2994
|
+
this.loadData();
|
|
2995
|
+
}
|
|
2996
|
+
onSearch(event) {
|
|
2997
|
+
const value = event.target.value;
|
|
2998
|
+
this.searchSubject.next(value);
|
|
2999
|
+
}
|
|
3000
|
+
onFilterChange(key, event) {
|
|
3001
|
+
const value = event.target.value;
|
|
3002
|
+
this.activeFilters[key] = value;
|
|
3003
|
+
this.currentPage = 1;
|
|
3004
|
+
this.filterChange.emit({ key, value });
|
|
3005
|
+
this.loadData();
|
|
3006
|
+
}
|
|
3007
|
+
onAction(action, row) {
|
|
3008
|
+
if (action.type === 'callback' && action.callback) {
|
|
3009
|
+
action.callback(row);
|
|
3010
|
+
}
|
|
3011
|
+
if (action.type === 'route' && action.route) {
|
|
3012
|
+
const url = this.replaceParams(action.route, row);
|
|
3013
|
+
this.router.navigateByUrl(url);
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3016
|
+
if (action.type === 'api') {
|
|
3017
|
+
if (action.confirmationNeeded) {
|
|
3018
|
+
const message = action.confirmationMessage || this.config.labels?.defaultConfirmationMessage || 'Are you sure?';
|
|
3019
|
+
if (!confirm(message))
|
|
3020
|
+
return;
|
|
3021
|
+
}
|
|
3022
|
+
this.action.emit({ action, row });
|
|
3023
|
+
}
|
|
3024
|
+
else {
|
|
3025
|
+
this.action.emit({ action, row });
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
onTopAction(action) {
|
|
3029
|
+
if (action.type === 'callback' && action.callback) {
|
|
3030
|
+
action.callback(null); // No row for top action
|
|
3031
|
+
}
|
|
3032
|
+
this.topAction.emit(action);
|
|
3033
|
+
}
|
|
3034
|
+
// --- Selection ---
|
|
3035
|
+
onSelectAll(event) {
|
|
3036
|
+
const checked = event.target.checked;
|
|
3037
|
+
this.data.forEach(row => row.selected = checked);
|
|
3038
|
+
this.updateSelectedRows();
|
|
3039
|
+
}
|
|
3040
|
+
onRowSelect(row) {
|
|
3041
|
+
this.updateSelectedRows();
|
|
3042
|
+
}
|
|
3043
|
+
updateSelectedRows() {
|
|
3044
|
+
this.selectedRows = this.data.filter(row => row.selected);
|
|
3045
|
+
this.rowSelect.emit(this.selectedRows);
|
|
3046
|
+
}
|
|
3047
|
+
// --- Helpers ---
|
|
3048
|
+
getCellValue(row, col) {
|
|
3049
|
+
// Support nested properties via labelPath or key
|
|
3050
|
+
const path = col.labelPath || col.key;
|
|
3051
|
+
let val = this.getValueByPath(row, path);
|
|
3052
|
+
// Formatting (Date, etc.)
|
|
3053
|
+
if (col.type === 'date' && val) {
|
|
3054
|
+
if (col.dateFormat) {
|
|
3055
|
+
try {
|
|
3056
|
+
return formatDate(val, col.dateFormat, this.locale);
|
|
3057
|
+
}
|
|
3058
|
+
catch (e) {
|
|
3059
|
+
console.warn('Invalid date format or value', val, col.dateFormat);
|
|
3060
|
+
return val;
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
return new Date(val).toLocaleDateString();
|
|
3064
|
+
}
|
|
3065
|
+
return val;
|
|
3066
|
+
}
|
|
3067
|
+
getBadgeClass(row, col) {
|
|
3068
|
+
const val = this.getCellValue(row, col);
|
|
3069
|
+
const strVal = String(val);
|
|
3070
|
+
// Config approach
|
|
3071
|
+
if (col.badgeConfig && col.badgeConfig[strVal]) {
|
|
3072
|
+
return `badge-${col.badgeConfig[strVal]}`;
|
|
3073
|
+
}
|
|
3074
|
+
// Default Logic
|
|
3075
|
+
const status = strVal.toLowerCase();
|
|
3076
|
+
if (['active', 'completed', 'success', 'approved'].includes(status))
|
|
3077
|
+
return 'badge-success';
|
|
3078
|
+
if (['pending', 'in progress', 'waiting'].includes(status))
|
|
3079
|
+
return 'badge-warning';
|
|
3080
|
+
if (['rejected', 'failed', 'error', 'deleted'].includes(status))
|
|
3081
|
+
return 'badge-danger';
|
|
3082
|
+
if (['draft', 'inactive'].includes(status))
|
|
3083
|
+
return 'badge-neutral';
|
|
3084
|
+
return 'badge-info'; // default
|
|
3085
|
+
}
|
|
3086
|
+
getSortIcon(key) {
|
|
3087
|
+
if (this.activeSort.key !== key)
|
|
3088
|
+
return 'fa-sort';
|
|
3089
|
+
return this.activeSort.direction === 'ASC' ? 'fa-sort-up' : 'fa-sort-down';
|
|
3090
|
+
}
|
|
3091
|
+
replaceParams(template, row) {
|
|
3092
|
+
return template.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => row[key] || '');
|
|
3093
|
+
}
|
|
3094
|
+
loadFilterOptions() {
|
|
3095
|
+
if (!this.config.filters)
|
|
3096
|
+
return;
|
|
3097
|
+
this.config.filters.forEach(filter => {
|
|
3098
|
+
if (filter.apiUrl && !filter.options) {
|
|
3099
|
+
this.http.get(filter.apiUrl).subscribe({
|
|
3100
|
+
next: (response) => {
|
|
3101
|
+
const data = filter.dataPath ? this.getValueByPath(response, filter.dataPath) : response;
|
|
3102
|
+
if (!Array.isArray(data)) {
|
|
3103
|
+
console.error(`Filter data for ${filter.key} is not an array`, data);
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
filter.options = data.map((item) => {
|
|
3107
|
+
const label = filter.labelPath ? this.getValueByPath(item, filter.labelPath) :
|
|
3108
|
+
(filter.labelKey ? item[filter.labelKey] : item.label || item.name);
|
|
3109
|
+
const value = filter.valuePath ? this.getValueByPath(item, filter.valuePath) :
|
|
3110
|
+
(filter.valueKey ? item[filter.valueKey] : item.value || item.code);
|
|
3111
|
+
return { label, value };
|
|
3112
|
+
});
|
|
3113
|
+
// Auto-populate matching column options
|
|
3114
|
+
if (this.config.columns) {
|
|
3115
|
+
const matchingColumn = this.config.columns.find(col => col.key === filter.key);
|
|
3116
|
+
if (matchingColumn && matchingColumn.dataType === 'select' && !matchingColumn.options) {
|
|
3117
|
+
matchingColumn.options = filter.options;
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
},
|
|
3121
|
+
error: (err) => console.error(`Failed to load filter options for ${filter.key}:`, err)
|
|
3122
|
+
});
|
|
3123
|
+
}
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
getValueByPath(obj, path) {
|
|
3127
|
+
if (!path || path === '')
|
|
3128
|
+
return obj;
|
|
3129
|
+
return path.split('.').reduce((acc, part) => {
|
|
3130
|
+
const match = part.match(/(\w+)\[(\d+)\]/);
|
|
3131
|
+
if (match) {
|
|
3132
|
+
return acc?.[match[1]]?.[parseInt(match[2])];
|
|
3133
|
+
}
|
|
3134
|
+
return acc?.[part];
|
|
3135
|
+
}, obj);
|
|
3136
|
+
}
|
|
3137
|
+
calculateStickyPositions() {
|
|
3138
|
+
// We calculate positions based on rendered widths
|
|
3139
|
+
if (!this.stickyHeaders || this.stickyHeaders.length === 0)
|
|
3140
|
+
return;
|
|
3141
|
+
this.stickyColumnStyles = {};
|
|
3142
|
+
let leftOffset = 0;
|
|
3143
|
+
this.hasStickyColumns = false;
|
|
3144
|
+
// Default sticky columns count is 3 if not specified
|
|
3145
|
+
const stickyCount = this.config.stickyColumnCount !== undefined ? this.config.stickyColumnCount : 3;
|
|
3146
|
+
// Checkbox width handling
|
|
3147
|
+
if (this.config.selectable) {
|
|
3148
|
+
const firstSticky = this.config.columns.find((c, i) => i < stickyCount || c.sticky);
|
|
3149
|
+
if (firstSticky) {
|
|
3150
|
+
// We can try to measure checkbox col if needed, but usually fixed 40px
|
|
3151
|
+
// Or better, if we have a checkbox col ref, assume 40px for now as it is fixed in CSS
|
|
3152
|
+
leftOffset = 40;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
const headerElements = this.stickyHeaders.toArray();
|
|
3156
|
+
this.config.columns.forEach((col, index) => {
|
|
3157
|
+
if (col.sticky || index < stickyCount) {
|
|
3158
|
+
col.sticky = true;
|
|
3159
|
+
this.hasStickyColumns = false; // Reset to true only after we set styles? No, wait.
|
|
3160
|
+
// Actually property is used in template to add class 'sticky-col'
|
|
3161
|
+
// but we need to update Styles based on previous cols widths
|
|
3162
|
+
// Find corresponding header element
|
|
3163
|
+
// Note: headerElements corresponds to columns indices
|
|
3164
|
+
const headerEl = headerElements[index]?.nativeElement;
|
|
3165
|
+
if (headerEl) {
|
|
3166
|
+
// Set style for current column
|
|
3167
|
+
this.stickyColumnStyles[col.key] = {
|
|
3168
|
+
left: `${leftOffset}px`
|
|
3169
|
+
// We DO NOT set width here, allowing it to be dynamic/auto
|
|
3170
|
+
};
|
|
3171
|
+
// Add THIS column's width to offset for the NEXT column
|
|
3172
|
+
// use getBoundingClientRect or offsetWidth
|
|
3173
|
+
leftOffset += headerEl.offsetWidth;
|
|
3174
|
+
}
|
|
3175
|
+
this.hasStickyColumns = true;
|
|
3176
|
+
}
|
|
3177
|
+
});
|
|
3178
|
+
this.cdr.detectChanges();
|
|
3179
|
+
}
|
|
3180
|
+
toTitleCase(str) {
|
|
3181
|
+
return str
|
|
3182
|
+
.replace(/([A-Z])/g, ' $1') // insert space before capital letters
|
|
3183
|
+
.replace(/^./, (str) => str.toUpperCase()) // capitalize the first letter
|
|
3184
|
+
.trim(); // remove any leading/trailing whitespace
|
|
3185
|
+
}
|
|
3186
|
+
get columnCount() {
|
|
3187
|
+
return this.config.columns.length;
|
|
3188
|
+
}
|
|
3189
|
+
onRowClick(row) {
|
|
3190
|
+
const hasSelection = window.getSelection()?.toString();
|
|
3191
|
+
if (this.config.clickableRows && !hasSelection) {
|
|
3192
|
+
this.rowClick.emit(row);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableComponent, deps: [{ token: i3.HttpClient }, { token: i1$1.Router }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
|
|
3196
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SmartTableComponent, isStandalone: false, selector: "lib-smart-table", inputs: { config: "config" }, outputs: { action: "action", topAction: "topAction", filterChange: "filterChange", rowSelect: "rowSelect", rowClick: "rowClick" }, viewQueries: [{ propertyName: "stickyHeaders", predicate: ["stickyHeader"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"smart-table-wrapper\">\n <!-- Top Toolbar -->\n <div class=\"st-toolbar\" *ngIf=\"config.searchConfig?.enabled || (config.filters && config.filters.length > 0) || (config.topBarButtons && config.topBarButtons.length > 0)\">\n \n <!-- Search -->\n <div class=\"st-search\" *ngIf=\"config.searchConfig?.enabled\">\n <i class=\"fa fa-search\"></i>\n <input type=\"text\" [placeholder]=\"config.labels?.searchPlaceholder || 'Search'\" (input)=\"onSearch($event)\">\n </div>\n\n <!-- Filters -->\n <div class=\"st-filters\" *ngIf=\"config.filters\">\n <div class=\"st-filter-item\" *ngFor=\"let filter of config.filters\">\n <select (change)=\"onFilterChange(filter.key, $event)\">\n <option value=\"\">{{ filter.label }}</option>\n <option *ngFor=\"let opt of filter.options\" [value]=\"opt.value\">{{ opt.label }}</option>\n </select>\n </div>\n </div>\n\n <!-- Top Bar Buttons -->\n <div class=\"st-actions\" *ngIf=\"config.topBarButtons\">\n <lib-button *ngFor=\"let btn of config.topBarButtons\" \n [variant]=\"btn.btnVariant || 'primary'\"\n [icon]=\"btn.icon || ''\"\n (click)=\"onTopAction(btn)\">\n {{ btn.label }}\n </lib-button>\n </div>\n </div>\n\n <!-- Table Container -->\n <div class=\"st-table-container\">\n <div class=\"st-check-loader\" *ngIf=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n <table class=\"st-table\" [class.loading-data]=\"loading\">\n <thead>\n <tr>\n <th *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" (change)=\"onSelectAll($event)\">\n </th>\n <th *ngFor=\"let col of config.columns\" \n #stickyHeader\n [class.sortable]=\"col.sortable\"\n [class.sticky-col]=\"col.sticky\"\n [ngStyle]=\"stickyColumnStyles[col.key]\"\n (click)=\"onSort(col)\">\n {{ col.label }}\n <span *ngIf=\"col.sortable\" class=\"sort-icon\">\n <i class=\"fa\" [ngClass]=\"getSortIcon(col.key)\"></i>\n </span>\n </th>\n <th *ngIf=\"config.actions && config.actions.length > 0\">{{ config.labels?.actionColumnHeader || 'Actions' }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of data\" \n [class.clickable-row]=\"config.clickableRows\" \n (click)=\"onRowClick(row)\">\n <td *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" [(ngModel)]=\"row.selected\" (change)=\"onRowSelect(row)\">\n </td>\n <td *ngFor=\"let col of config.columns\" [class.sticky-col]=\"col.sticky\" [ngStyle]=\"stickyColumnStyles[col.key]\">\n <!-- Text/Number/Date -->\n <span *ngIf=\"col.type !== 'custom' && col.type !== 'html' && col.type !== 'badge'\">\n {{ getCellValue(row, col) }}\n </span>\n <!-- HTML -->\n <div *ngIf=\"col.type === 'html'\" [innerHTML]=\"getCellValue(row, col)\"></div>\n <!-- Badge -->\n <span *ngIf=\"col.type === 'badge'\" class=\"st-badge\" [ngClass]=\"getBadgeClass(row, col)\">\n {{ getCellValue(row, col) }}\n </span>\n </td>\n \n <!-- Row Actions -->\n <td *ngIf=\"config.actions && config.actions.length > 0\" class=\"st-row-actions\">\n <div class=\"action-buttons\">\n <ng-container *ngFor=\"let action of config.actions\">\n <lib-button \n [variant]=\"action.btnVariant || 'secondary'\"\n [icon]=\"action.icon || ''\"\n (click)=\"onAction(action, row)\">\n {{ action.label }}\n </lib-button>\n </ng-container>\n </div>\n <!-- Alternatively use specific icons if needed, but button component is requested -->\n </td>\n </tr>\n <tr *ngIf=\"data.length === 0 && !loading\">\n <td [attr.colspan]=\"columnCount + (config.selectable ? 1 : 0) + (config.actions ? 1 : 0)\" class=\"no-data\">\n {{ config.labels?.noDataMessage || 'No data available' }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n <div class=\"st-pagination\" *ngIf=\"config.pagination && config.pagination.enabled\">\n <lib-pagination\n [totalItems]=\"totalItems\"\n [itemsPerPage]=\"config.pagination.pageSize\"\n [currentPage]=\"currentPage\"\n [pageSizeOptions]=\"config.pagination.pageSizeOptions\"\n (pageChange)=\"onPageChange($event)\"\n (itemsPerPageChange)=\"onPageSizeChange($event)\">\n </lib-pagination>\n </div>\n</div>\n", styles: [".smart-table-wrapper{font-family:var(--st-font-family, \"Roboto\", sans-serif);background:var(--st-table-bg, #fff);border-radius:var(--st-border-radius, 8px);box-shadow:var(--st-box-shadow, 0 2px 4px rgba(0, 0, 0, .05));display:flex;flex-direction:column;gap:0;padding:0;border:var(--st-table-border, 1px solid #e0e0e0);overflow:hidden}.st-toolbar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;padding:var(--st-toolbar-padding, 1rem);background:var(--st-toolbar-bg, #fff);border-bottom:var(--st-toolbar-border-bottom, 1px solid #eee);gap:var(--st-toolbar-gap, 1rem)}.st-toolbar .st-search{position:relative;width:var(--st-search-width, auto)}.st-toolbar .st-search input{padding:var(--st-search-padding, .5rem .5rem .5rem 2rem);border:var(--st-search-border, 1px solid #ccc);border-radius:var(--st-search-radius, 4px);background:var(--st-search-bg, #fff);font-size:var(--st-font-size, 14px);width:100%;color:var(--st-text-color, #333)}.st-toolbar .st-search i{position:absolute;left:.75rem;top:50%;transform:translateY(-50%);color:var(--st-search-icon-color, #999)}.st-toolbar .st-filters{display:flex;gap:1rem}.st-toolbar .st-filters select{padding:var(--st-filter-padding, .5rem);border:var(--st-filter-border, 1px solid #ccc);border-radius:var(--st-filter-radius, 4px);font-size:var(--st-filter-font-size, 14px);background:var(--st-filter-bg, #fff);color:var(--st-filter-color, #333)}.st-toolbar .st-actions{display:flex;gap:.5rem}.st-table-container{overflow-x:auto;overflow-y:auto;padding:var(--st-table-padding, 1rem)}.st-table-container::-webkit-scrollbar{width:var(--st-scrollbar-width, 8px);height:var(--st-scrollbar-height, 8px)}.st-table-container::-webkit-scrollbar-track{background:var(--st-scrollbar-track-bg, #f1f1f1);border-radius:var(--st-scrollbar-track-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb{background:var(--st-scrollbar-thumb-bg, #c1c1c1);border-radius:var(--st-scrollbar-thumb-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb:hover{background:var(--st-scrollbar-thumb-hover-bg, #a8a8a8)}.st-table-container.has-sticky-header .st-table thead th{position:sticky;top:0;z-index:10;background:var(--st-header-bg, #f9f9f9);box-shadow:0 1px 2px -1px #0000001a}.st-table-container table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--st-font-size, 14px)}.st-table-container table thead{background:var(--st-header-bg, #f9f9f9)}.st-table-container table thead th{padding:.75rem 1rem;text-align:left;color:var(--st-header-color, #333);font-weight:var(--st-header-weight, 500);font-size:var(--st-header-size, 14px);text-transform:var(--st-header-transform, none);border-bottom:var(--st-header-border, 1px solid #eee);white-space:nowrap}.st-table-container table thead th.sortable{cursor:pointer}.st-table-container table thead th.sortable:hover{opacity:.8}.st-table-container table thead th .sort-icon{margin-left:.5rem}.st-table-container table thead th .sort-icon .sort-icon{margin-left:.5rem;font-size:var(--st-sort-icon-size, .8em)}.st-table-container table thead th.st-checkbox-col{width:40px}.st-table-container table thead th.sticky-col{position:sticky;z-index:3;background:var(--st-header-bg, #f9f9f9);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table thead th.sticky-col:first-child{left:0}.st-table-container table tbody tr{background:var(--st-row-bg, #fff)}.st-table-container table tbody tr td{padding:var(--st-cell-padding, 1rem);color:var(--st-text-color, #333);vertical-align:middle;border-bottom:var(--st-row-border, 1px solid #eee)}.st-table-container table tbody tr td.sticky-col{position:sticky;z-index:2;background:var(--st-row-bg, #fff);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table tbody tr td.sticky-col:first-child{left:0}.st-table-container table tbody tr:hover td,.st-table-container table tbody tr:hover td.sticky-col{background:var(--st-row-hover-bg, #f9f9f9)}.st-table-container table tbody tr.selected td,.st-table-container table tbody tr.selected td.sticky-col{background:var(--st-row-selected-bg, #f3e5f5)}.st-table-container table tbody tr.clickable-row,.st-table-container table tbody tr.clickable-row td{cursor:pointer}.st-table-container table tbody tr.clickable-row:hover td,.st-table-container table tbody tr.clickable-row:hover td.sticky-col{background:var(--st-row-hover-bg, #f5f5f5)}input[type=checkbox]{accent-color:var(--st-checkbox-color, #6200EE);width:var(--st-checkbox-size, 16px);height:var(--st-checkbox-size, 16px);cursor:pointer}.st-badge{display:inline-block;padding:var(--st-badge-padding, 4px 12px);border-radius:var(--st-badge-radius, 12px);font-size:var(--st-badge-font-size, 12px);font-weight:var(--st-badge-font-weight, 500);text-align:center;white-space:nowrap}.st-badge.badge-success{background:var(--st-badge-success-bg, #e8f5e9);color:var(--st-badge-success-color, #2e7d32)}.st-badge.badge-warning{background:var(--st-badge-warning-bg, #fff3e0);color:var(--st-badge-warning-color, #ef6c00)}.st-badge.badge-danger{background:var(--st-badge-danger-bg, #ffebee);color:var(--st-badge-danger-color, #c62828)}.st-badge.badge-info{background:var(--st-badge-info-bg, #e3f2fd);color:var(--st-badge-info-color, #1565c0)}.st-badge.badge-neutral{background:var(--st-badge-neutral-bg, #f5f5f5);color:var(--st-badge-neutral-color, #616161)}.st-row-actions .action-buttons{display:flex;gap:.5rem;align-items:center}.no-data{text-align:center;padding:2rem;color:var(--st-no-data-color, #888)}.st-pagination{padding:var(--st-pagination-padding, 1rem);border-top:var(--st-pagination-border-top, none)}@media(max-width:768px){.st-toolbar{flex-direction:column;align-items:stretch}.st-toolbar .st-search,.st-toolbar .st-filters,.st-toolbar .st-actions,.st-toolbar .st-search input{width:100%}}.st-table-container{position:relative;min-height:200px}.st-table-container .st-table.loading-data{opacity:.5;pointer-events:none}.st-table-container .st-check-loader{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:10}.st-table-container .st-check-loader .spinner{width:40px;height:40px;border:4px solid var(--st-spinner-border-color, rgba(0, 0, 0, .1));border-left-color:var(--st-loader-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: PaginationComponent, selector: "lib-pagination", inputs: ["totalItems", "itemsPerPage", "currentPage", "pageSizeOptions", "theme", "labels"], outputs: ["pageChange", "itemsPerPageChange"] }, { kind: "component", type: ButtonComponent, selector: "lib-button", inputs: ["variant", "type", "disabled", "width", "height", "borderRadius", "fontSize", "fontWeight", "backgroundColor", "color", "border", "icon"] }] });
|
|
3197
|
+
}
|
|
3198
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableComponent, decorators: [{
|
|
3199
|
+
type: Component,
|
|
3200
|
+
args: [{ selector: 'lib-smart-table', standalone: false, template: "<div class=\"smart-table-wrapper\">\n <!-- Top Toolbar -->\n <div class=\"st-toolbar\" *ngIf=\"config.searchConfig?.enabled || (config.filters && config.filters.length > 0) || (config.topBarButtons && config.topBarButtons.length > 0)\">\n \n <!-- Search -->\n <div class=\"st-search\" *ngIf=\"config.searchConfig?.enabled\">\n <i class=\"fa fa-search\"></i>\n <input type=\"text\" [placeholder]=\"config.labels?.searchPlaceholder || 'Search'\" (input)=\"onSearch($event)\">\n </div>\n\n <!-- Filters -->\n <div class=\"st-filters\" *ngIf=\"config.filters\">\n <div class=\"st-filter-item\" *ngFor=\"let filter of config.filters\">\n <select (change)=\"onFilterChange(filter.key, $event)\">\n <option value=\"\">{{ filter.label }}</option>\n <option *ngFor=\"let opt of filter.options\" [value]=\"opt.value\">{{ opt.label }}</option>\n </select>\n </div>\n </div>\n\n <!-- Top Bar Buttons -->\n <div class=\"st-actions\" *ngIf=\"config.topBarButtons\">\n <lib-button *ngFor=\"let btn of config.topBarButtons\" \n [variant]=\"btn.btnVariant || 'primary'\"\n [icon]=\"btn.icon || ''\"\n (click)=\"onTopAction(btn)\">\n {{ btn.label }}\n </lib-button>\n </div>\n </div>\n\n <!-- Table Container -->\n <div class=\"st-table-container\">\n <div class=\"st-check-loader\" *ngIf=\"loading\">\n <div class=\"spinner\"></div>\n </div>\n <table class=\"st-table\" [class.loading-data]=\"loading\">\n <thead>\n <tr>\n <th *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" (change)=\"onSelectAll($event)\">\n </th>\n <th *ngFor=\"let col of config.columns\" \n #stickyHeader\n [class.sortable]=\"col.sortable\"\n [class.sticky-col]=\"col.sticky\"\n [ngStyle]=\"stickyColumnStyles[col.key]\"\n (click)=\"onSort(col)\">\n {{ col.label }}\n <span *ngIf=\"col.sortable\" class=\"sort-icon\">\n <i class=\"fa\" [ngClass]=\"getSortIcon(col.key)\"></i>\n </span>\n </th>\n <th *ngIf=\"config.actions && config.actions.length > 0\">{{ config.labels?.actionColumnHeader || 'Actions' }}</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let row of data\" \n [class.clickable-row]=\"config.clickableRows\" \n (click)=\"onRowClick(row)\">\n <td *ngIf=\"config.selectable\" class=\"st-checkbox-col\">\n <input type=\"checkbox\" [(ngModel)]=\"row.selected\" (change)=\"onRowSelect(row)\">\n </td>\n <td *ngFor=\"let col of config.columns\" [class.sticky-col]=\"col.sticky\" [ngStyle]=\"stickyColumnStyles[col.key]\">\n <!-- Text/Number/Date -->\n <span *ngIf=\"col.type !== 'custom' && col.type !== 'html' && col.type !== 'badge'\">\n {{ getCellValue(row, col) }}\n </span>\n <!-- HTML -->\n <div *ngIf=\"col.type === 'html'\" [innerHTML]=\"getCellValue(row, col)\"></div>\n <!-- Badge -->\n <span *ngIf=\"col.type === 'badge'\" class=\"st-badge\" [ngClass]=\"getBadgeClass(row, col)\">\n {{ getCellValue(row, col) }}\n </span>\n </td>\n \n <!-- Row Actions -->\n <td *ngIf=\"config.actions && config.actions.length > 0\" class=\"st-row-actions\">\n <div class=\"action-buttons\">\n <ng-container *ngFor=\"let action of config.actions\">\n <lib-button \n [variant]=\"action.btnVariant || 'secondary'\"\n [icon]=\"action.icon || ''\"\n (click)=\"onAction(action, row)\">\n {{ action.label }}\n </lib-button>\n </ng-container>\n </div>\n <!-- Alternatively use specific icons if needed, but button component is requested -->\n </td>\n </tr>\n <tr *ngIf=\"data.length === 0 && !loading\">\n <td [attr.colspan]=\"columnCount + (config.selectable ? 1 : 0) + (config.actions ? 1 : 0)\" class=\"no-data\">\n {{ config.labels?.noDataMessage || 'No data available' }}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n <div class=\"st-pagination\" *ngIf=\"config.pagination && config.pagination.enabled\">\n <lib-pagination\n [totalItems]=\"totalItems\"\n [itemsPerPage]=\"config.pagination.pageSize\"\n [currentPage]=\"currentPage\"\n [pageSizeOptions]=\"config.pagination.pageSizeOptions\"\n (pageChange)=\"onPageChange($event)\"\n (itemsPerPageChange)=\"onPageSizeChange($event)\">\n </lib-pagination>\n </div>\n</div>\n", styles: [".smart-table-wrapper{font-family:var(--st-font-family, \"Roboto\", sans-serif);background:var(--st-table-bg, #fff);border-radius:var(--st-border-radius, 8px);box-shadow:var(--st-box-shadow, 0 2px 4px rgba(0, 0, 0, .05));display:flex;flex-direction:column;gap:0;padding:0;border:var(--st-table-border, 1px solid #e0e0e0);overflow:hidden}.st-toolbar{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;padding:var(--st-toolbar-padding, 1rem);background:var(--st-toolbar-bg, #fff);border-bottom:var(--st-toolbar-border-bottom, 1px solid #eee);gap:var(--st-toolbar-gap, 1rem)}.st-toolbar .st-search{position:relative;width:var(--st-search-width, auto)}.st-toolbar .st-search input{padding:var(--st-search-padding, .5rem .5rem .5rem 2rem);border:var(--st-search-border, 1px solid #ccc);border-radius:var(--st-search-radius, 4px);background:var(--st-search-bg, #fff);font-size:var(--st-font-size, 14px);width:100%;color:var(--st-text-color, #333)}.st-toolbar .st-search i{position:absolute;left:.75rem;top:50%;transform:translateY(-50%);color:var(--st-search-icon-color, #999)}.st-toolbar .st-filters{display:flex;gap:1rem}.st-toolbar .st-filters select{padding:var(--st-filter-padding, .5rem);border:var(--st-filter-border, 1px solid #ccc);border-radius:var(--st-filter-radius, 4px);font-size:var(--st-filter-font-size, 14px);background:var(--st-filter-bg, #fff);color:var(--st-filter-color, #333)}.st-toolbar .st-actions{display:flex;gap:.5rem}.st-table-container{overflow-x:auto;overflow-y:auto;padding:var(--st-table-padding, 1rem)}.st-table-container::-webkit-scrollbar{width:var(--st-scrollbar-width, 8px);height:var(--st-scrollbar-height, 8px)}.st-table-container::-webkit-scrollbar-track{background:var(--st-scrollbar-track-bg, #f1f1f1);border-radius:var(--st-scrollbar-track-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb{background:var(--st-scrollbar-thumb-bg, #c1c1c1);border-radius:var(--st-scrollbar-thumb-radius, 4px)}.st-table-container::-webkit-scrollbar-thumb:hover{background:var(--st-scrollbar-thumb-hover-bg, #a8a8a8)}.st-table-container.has-sticky-header .st-table thead th{position:sticky;top:0;z-index:10;background:var(--st-header-bg, #f9f9f9);box-shadow:0 1px 2px -1px #0000001a}.st-table-container table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--st-font-size, 14px)}.st-table-container table thead{background:var(--st-header-bg, #f9f9f9)}.st-table-container table thead th{padding:.75rem 1rem;text-align:left;color:var(--st-header-color, #333);font-weight:var(--st-header-weight, 500);font-size:var(--st-header-size, 14px);text-transform:var(--st-header-transform, none);border-bottom:var(--st-header-border, 1px solid #eee);white-space:nowrap}.st-table-container table thead th.sortable{cursor:pointer}.st-table-container table thead th.sortable:hover{opacity:.8}.st-table-container table thead th .sort-icon{margin-left:.5rem}.st-table-container table thead th .sort-icon .sort-icon{margin-left:.5rem;font-size:var(--st-sort-icon-size, .8em)}.st-table-container table thead th.st-checkbox-col{width:40px}.st-table-container table thead th.sticky-col{position:sticky;z-index:3;background:var(--st-header-bg, #f9f9f9);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table thead th.sticky-col:first-child{left:0}.st-table-container table tbody tr{background:var(--st-row-bg, #fff)}.st-table-container table tbody tr td{padding:var(--st-cell-padding, 1rem);color:var(--st-text-color, #333);vertical-align:middle;border-bottom:var(--st-row-border, 1px solid #eee)}.st-table-container table tbody tr td.sticky-col{position:sticky;z-index:2;background:var(--st-row-bg, #fff);box-shadow:var(--st-sticky-shadow, 2px 0 5px -2px rgba(0, 0, 0, .1));border-right:var(--st-sticky-border-right, 1px solid rgba(0, 0, 0, .05))}.st-table-container table tbody tr td.sticky-col:first-child{left:0}.st-table-container table tbody tr:hover td,.st-table-container table tbody tr:hover td.sticky-col{background:var(--st-row-hover-bg, #f9f9f9)}.st-table-container table tbody tr.selected td,.st-table-container table tbody tr.selected td.sticky-col{background:var(--st-row-selected-bg, #f3e5f5)}.st-table-container table tbody tr.clickable-row,.st-table-container table tbody tr.clickable-row td{cursor:pointer}.st-table-container table tbody tr.clickable-row:hover td,.st-table-container table tbody tr.clickable-row:hover td.sticky-col{background:var(--st-row-hover-bg, #f5f5f5)}input[type=checkbox]{accent-color:var(--st-checkbox-color, #6200EE);width:var(--st-checkbox-size, 16px);height:var(--st-checkbox-size, 16px);cursor:pointer}.st-badge{display:inline-block;padding:var(--st-badge-padding, 4px 12px);border-radius:var(--st-badge-radius, 12px);font-size:var(--st-badge-font-size, 12px);font-weight:var(--st-badge-font-weight, 500);text-align:center;white-space:nowrap}.st-badge.badge-success{background:var(--st-badge-success-bg, #e8f5e9);color:var(--st-badge-success-color, #2e7d32)}.st-badge.badge-warning{background:var(--st-badge-warning-bg, #fff3e0);color:var(--st-badge-warning-color, #ef6c00)}.st-badge.badge-danger{background:var(--st-badge-danger-bg, #ffebee);color:var(--st-badge-danger-color, #c62828)}.st-badge.badge-info{background:var(--st-badge-info-bg, #e3f2fd);color:var(--st-badge-info-color, #1565c0)}.st-badge.badge-neutral{background:var(--st-badge-neutral-bg, #f5f5f5);color:var(--st-badge-neutral-color, #616161)}.st-row-actions .action-buttons{display:flex;gap:.5rem;align-items:center}.no-data{text-align:center;padding:2rem;color:var(--st-no-data-color, #888)}.st-pagination{padding:var(--st-pagination-padding, 1rem);border-top:var(--st-pagination-border-top, none)}@media(max-width:768px){.st-toolbar{flex-direction:column;align-items:stretch}.st-toolbar .st-search,.st-toolbar .st-filters,.st-toolbar .st-actions,.st-toolbar .st-search input{width:100%}}.st-table-container{position:relative;min-height:200px}.st-table-container .st-table.loading-data{opacity:.5;pointer-events:none}.st-table-container .st-check-loader{position:absolute;top:0;left:0;width:100%;height:100%;display:flex;justify-content:center;align-items:center;z-index:10}.st-table-container .st-check-loader .spinner{width:40px;height:40px;border:4px solid var(--st-spinner-border-color, rgba(0, 0, 0, .1));border-left-color:var(--st-loader-color);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
|
|
3201
|
+
}], ctorParameters: () => [{ type: i3.HttpClient }, { type: i1$1.Router }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }], propDecorators: { config: [{
|
|
3202
|
+
type: Input
|
|
3203
|
+
}], action: [{
|
|
3204
|
+
type: Output
|
|
3205
|
+
}], topAction: [{
|
|
3206
|
+
type: Output
|
|
3207
|
+
}], filterChange: [{
|
|
3208
|
+
type: Output
|
|
3209
|
+
}], rowSelect: [{
|
|
3210
|
+
type: Output
|
|
3211
|
+
}], rowClick: [{
|
|
3212
|
+
type: Output
|
|
3213
|
+
}], stickyHeaders: [{
|
|
3214
|
+
type: ViewChildren,
|
|
3215
|
+
args: ['stickyHeader']
|
|
3216
|
+
}] } });
|
|
3217
|
+
|
|
3218
|
+
class SmartTableModule {
|
|
3219
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
3220
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SmartTableModule, declarations: [SmartTableComponent], imports: [CommonModule,
|
|
3221
|
+
FormsModule,
|
|
3222
|
+
PaginationModule,
|
|
3223
|
+
ButtonModule], exports: [SmartTableComponent] });
|
|
3224
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableModule, imports: [CommonModule,
|
|
3225
|
+
FormsModule,
|
|
3226
|
+
PaginationModule,
|
|
3227
|
+
ButtonModule] });
|
|
3228
|
+
}
|
|
3229
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SmartTableModule, decorators: [{
|
|
3230
|
+
type: NgModule,
|
|
3231
|
+
args: [{
|
|
3232
|
+
declarations: [
|
|
3233
|
+
SmartTableComponent
|
|
3234
|
+
],
|
|
3235
|
+
imports: [
|
|
3236
|
+
CommonModule,
|
|
3237
|
+
FormsModule,
|
|
3238
|
+
PaginationModule,
|
|
3239
|
+
ButtonModule
|
|
3240
|
+
],
|
|
3241
|
+
exports: [
|
|
3242
|
+
SmartTableComponent
|
|
3243
|
+
]
|
|
3244
|
+
}]
|
|
3245
|
+
}] });
|
|
3246
|
+
|
|
3247
|
+
const SAMPLE_FORMS = {
|
|
3248
|
+
// Simple Contact Form
|
|
3249
|
+
contactForm: `{
|
|
3250
|
+
"entityType": "CONTACT",
|
|
3251
|
+
"label": "Contact Us",
|
|
3252
|
+
"description": "Send us a message",
|
|
3253
|
+
"formType": "SECTION",
|
|
3254
|
+
"sectionConfig": {
|
|
3255
|
+
"children": [
|
|
3256
|
+
{
|
|
3257
|
+
"name": "name",
|
|
3258
|
+
"label": "Full Name",
|
|
3259
|
+
"type": "TEXT_INPUT",
|
|
3260
|
+
"subType": "SHORT",
|
|
3261
|
+
"required": true,
|
|
3262
|
+
"hint": "Enter your full name"
|
|
3263
|
+
},
|
|
3264
|
+
{
|
|
3265
|
+
"name": "email",
|
|
3266
|
+
"label": "Email Address",
|
|
3267
|
+
"type": "TEXT_INPUT",
|
|
3268
|
+
"subType": "EMAIL",
|
|
3269
|
+
"required": true,
|
|
3270
|
+
"hint": "your.email@example.com"
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
"name": "message",
|
|
3274
|
+
"label": "Message",
|
|
3275
|
+
"type": "TEXT_INPUT",
|
|
3276
|
+
"subType": "LONG",
|
|
3277
|
+
"required": true,
|
|
3278
|
+
"textConfig": {
|
|
3279
|
+
"length": {
|
|
3280
|
+
"min": 10,
|
|
3281
|
+
"max": 500
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
]
|
|
3286
|
+
}
|
|
3287
|
+
}`,
|
|
3288
|
+
// User Registration with Stepper
|
|
3289
|
+
registrationForm: `{
|
|
3290
|
+
"entityType": "USER",
|
|
3291
|
+
"label": "User Registration",
|
|
3292
|
+
"description": "Create your account",
|
|
3293
|
+
"formType": "STEPPER",
|
|
3294
|
+
"stepperConfig": {
|
|
3295
|
+
"children": [
|
|
3296
|
+
{
|
|
3297
|
+
"type": "GROUP",
|
|
3298
|
+
"subType": "SECTION",
|
|
3299
|
+
"sectionConfig": {
|
|
3300
|
+
"label": "Personal Information",
|
|
3301
|
+
"children": [
|
|
3302
|
+
{
|
|
3303
|
+
"type": "ROW",
|
|
3304
|
+
"subType": "HORIZONTAL",
|
|
3305
|
+
"children": [
|
|
3306
|
+
{
|
|
3307
|
+
"name": "firstName",
|
|
3308
|
+
"label": "First Name",
|
|
3309
|
+
"type": "TEXT_INPUT",
|
|
3310
|
+
"subType": "SHORT",
|
|
3311
|
+
"required": true
|
|
3312
|
+
},
|
|
3313
|
+
{
|
|
3314
|
+
"name": "lastName",
|
|
3315
|
+
"label": "Last Name",
|
|
3316
|
+
"type": "TEXT_INPUT",
|
|
3317
|
+
"subType": "SHORT",
|
|
3318
|
+
"required": true
|
|
3319
|
+
}
|
|
3320
|
+
]
|
|
3321
|
+
},
|
|
3322
|
+
{
|
|
3323
|
+
"name": "fullName",
|
|
3324
|
+
"label": "Full Name",
|
|
3325
|
+
"type": "GENERATED",
|
|
3326
|
+
"subType": "FORMULA",
|
|
3327
|
+
"generatedConfig": {
|
|
3328
|
+
"formula": "function fullName(first, last) { return (first || '') + ' ' + (last || ''); }",
|
|
3329
|
+
"variables": ["firstName", "lastName"]
|
|
3330
|
+
}
|
|
3331
|
+
},
|
|
3332
|
+
{
|
|
3333
|
+
"name": "dateOfBirth",
|
|
3334
|
+
"label": "Date of Birth",
|
|
3335
|
+
"type": "DATE",
|
|
3336
|
+
"subType": "SINGLE",
|
|
3337
|
+
"required": true,
|
|
3338
|
+
"dateConfig": {
|
|
3339
|
+
"allowFuture": false
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
]
|
|
3343
|
+
}
|
|
3344
|
+
},
|
|
3345
|
+
{
|
|
3346
|
+
"type": "GROUP",
|
|
3347
|
+
"subType": "SECTION",
|
|
3348
|
+
"sectionConfig": {
|
|
3349
|
+
"label": "Contact Information",
|
|
3350
|
+
"children": [
|
|
3351
|
+
{
|
|
3352
|
+
"name": "email",
|
|
3353
|
+
"label": "Email",
|
|
3354
|
+
"type": "TEXT_INPUT",
|
|
3355
|
+
"subType": "EMAIL",
|
|
3356
|
+
"required": true
|
|
3357
|
+
},
|
|
3358
|
+
{
|
|
3359
|
+
"name": "phone",
|
|
3360
|
+
"label": "Phone Number",
|
|
3361
|
+
"type": "TEXT_INPUT",
|
|
3362
|
+
"subType": "PHONE",
|
|
3363
|
+
"required": true
|
|
3364
|
+
}
|
|
3365
|
+
]
|
|
3366
|
+
}
|
|
3367
|
+
},
|
|
3368
|
+
{
|
|
3369
|
+
"type": "GROUP",
|
|
3370
|
+
"subType": "SECTION",
|
|
3371
|
+
"sectionConfig": {
|
|
3372
|
+
"label": "Preferences",
|
|
3373
|
+
"children": [
|
|
3374
|
+
{
|
|
3375
|
+
"name": "notifications",
|
|
3376
|
+
"label": "Enable Email Notifications",
|
|
3377
|
+
"type": "SWITCH",
|
|
3378
|
+
"subType": "BOOL",
|
|
3379
|
+
"defaultValue": true
|
|
3380
|
+
},
|
|
3381
|
+
{
|
|
3382
|
+
"name": "interests",
|
|
3383
|
+
"label": "Interests",
|
|
3384
|
+
"type": "CHIP",
|
|
3385
|
+
"subType": "MULTIPLE",
|
|
3386
|
+
"optionConfig": {
|
|
3387
|
+
"optionList": [
|
|
3388
|
+
{ "label": "Technology", "code": "TECH" },
|
|
3389
|
+
{ "label": "Sports", "code": "SPORTS" },
|
|
3390
|
+
{ "label": "Music", "code": "MUSIC" },
|
|
3391
|
+
{ "label": "Travel", "code": "TRAVEL" }
|
|
3392
|
+
]
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
]
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
],
|
|
3399
|
+
"showStep": true,
|
|
3400
|
+
"isHorizontal": true
|
|
3401
|
+
}
|
|
3402
|
+
}`,
|
|
3403
|
+
// Survey Form with Conditional Fields
|
|
3404
|
+
surveyForm: `{
|
|
3405
|
+
"entityType": "SURVEY",
|
|
3406
|
+
"label": "Customer Satisfaction Survey",
|
|
3407
|
+
"formType": "SECTION",
|
|
3408
|
+
"sectionConfig": {
|
|
3409
|
+
"children": [
|
|
3410
|
+
{
|
|
3411
|
+
"name": "overallRating",
|
|
3412
|
+
"label": "Overall Experience",
|
|
3413
|
+
"type": "RATING",
|
|
3414
|
+
"subType": "STAR",
|
|
3415
|
+
"required": true,
|
|
3416
|
+
"ratingConfig": {
|
|
3417
|
+
"maxRating": 5,
|
|
3418
|
+
"allowHalf": false
|
|
3419
|
+
}
|
|
3420
|
+
},
|
|
3421
|
+
{
|
|
3422
|
+
"name": "wouldRecommend",
|
|
3423
|
+
"label": "Would you recommend us?",
|
|
3424
|
+
"type": "RADIO",
|
|
3425
|
+
"subType": "SINGLE",
|
|
3426
|
+
"required": true,
|
|
3427
|
+
"optionConfig": {
|
|
3428
|
+
"optionList": [
|
|
3429
|
+
{ "label": "Yes", "code": "YES" },
|
|
3430
|
+
{ "label": "No", "code": "NO" },
|
|
3431
|
+
{ "label": "Maybe", "code": "MAYBE" }
|
|
3432
|
+
]
|
|
3433
|
+
}
|
|
3434
|
+
},
|
|
3435
|
+
{
|
|
3436
|
+
"name": "reasonForNo",
|
|
3437
|
+
"label": "Why not?",
|
|
3438
|
+
"type": "TEXT_INPUT",
|
|
3439
|
+
"subType": "LONG",
|
|
3440
|
+
"visibilityExpression": "wouldRecommend === 'NO'",
|
|
3441
|
+
"required": true
|
|
3442
|
+
},
|
|
3443
|
+
{
|
|
3444
|
+
"name": "improvements",
|
|
3445
|
+
"label": "What can we improve?",
|
|
3446
|
+
"type": "CHECKBOX",
|
|
3447
|
+
"subType": "LIST",
|
|
3448
|
+
"optionConfig": {
|
|
3449
|
+
"optionList": [
|
|
3450
|
+
{ "label": "Customer Service", "code": "SERVICE" },
|
|
3451
|
+
{ "label": "Product Quality", "code": "QUALITY" },
|
|
3452
|
+
{ "label": "Pricing", "code": "PRICE" },
|
|
3453
|
+
{ "label": "Delivery Speed", "code": "DELIVERY" }
|
|
3454
|
+
]
|
|
3455
|
+
}
|
|
3456
|
+
},
|
|
3457
|
+
{
|
|
3458
|
+
"name": "additionalComments",
|
|
3459
|
+
"label": "Additional Comments",
|
|
3460
|
+
"type": "TEXT_INPUT",
|
|
3461
|
+
"subType": "LONG",
|
|
3462
|
+
"textConfig": {
|
|
3463
|
+
"length": {
|
|
3464
|
+
"max": 1000
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
]
|
|
3469
|
+
}
|
|
3470
|
+
}`,
|
|
3471
|
+
// Job Application Form
|
|
3472
|
+
jobApplicationForm: `{
|
|
3473
|
+
"entityType": "JOB_APPLICATION",
|
|
3474
|
+
"label": "Job Application",
|
|
3475
|
+
"description": "Apply for a position at our company",
|
|
3476
|
+
"formType": "STEPPER",
|
|
3477
|
+
"stepperConfig": {
|
|
3478
|
+
"children": [
|
|
3479
|
+
{
|
|
3480
|
+
"type": "GROUP",
|
|
3481
|
+
"subType": "SECTION",
|
|
3482
|
+
"sectionConfig": {
|
|
3483
|
+
"label": "Personal Details",
|
|
3484
|
+
"children": [
|
|
3485
|
+
{
|
|
3486
|
+
"type": "ROW",
|
|
3487
|
+
"subType": "HORIZONTAL",
|
|
3488
|
+
"children": [
|
|
3489
|
+
{
|
|
3490
|
+
"name": "firstName",
|
|
3491
|
+
"label": "First Name",
|
|
3492
|
+
"type": "TEXT_INPUT",
|
|
3493
|
+
"subType": "SHORT",
|
|
3494
|
+
"required": true
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
"name": "lastName",
|
|
3498
|
+
"label": "Last Name",
|
|
3499
|
+
"type": "TEXT_INPUT",
|
|
3500
|
+
"subType": "SHORT",
|
|
3501
|
+
"required": true
|
|
3502
|
+
}
|
|
3503
|
+
]
|
|
3504
|
+
},
|
|
3505
|
+
{
|
|
3506
|
+
"name": "email",
|
|
3507
|
+
"label": "Email",
|
|
3508
|
+
"type": "TEXT_INPUT",
|
|
3509
|
+
"subType": "EMAIL",
|
|
3510
|
+
"required": true
|
|
3511
|
+
},
|
|
3512
|
+
{
|
|
3513
|
+
"name": "phone",
|
|
3514
|
+
"label": "Phone",
|
|
3515
|
+
"type": "TEXT_INPUT",
|
|
3516
|
+
"subType": "PHONE",
|
|
3517
|
+
"required": true
|
|
3518
|
+
}
|
|
3519
|
+
]
|
|
3520
|
+
}
|
|
3521
|
+
},
|
|
3522
|
+
{
|
|
3523
|
+
"type": "GROUP",
|
|
3524
|
+
"subType": "SECTION",
|
|
3525
|
+
"sectionConfig": {
|
|
3526
|
+
"label": "Experience",
|
|
3527
|
+
"allowMulti": true,
|
|
3528
|
+
"name": "experienceList",
|
|
3529
|
+
"children": [
|
|
3530
|
+
{
|
|
3531
|
+
"name": "company",
|
|
3532
|
+
"label": "Company Name",
|
|
3533
|
+
"type": "TEXT_INPUT",
|
|
3534
|
+
"subType": "SHORT",
|
|
3535
|
+
"required": true
|
|
3536
|
+
},
|
|
3537
|
+
{
|
|
3538
|
+
"name": "position",
|
|
3539
|
+
"label": "Position",
|
|
3540
|
+
"type": "TEXT_INPUT",
|
|
3541
|
+
"subType": "SHORT",
|
|
3542
|
+
"required": true
|
|
3543
|
+
},
|
|
3544
|
+
{
|
|
3545
|
+
"type": "ROW",
|
|
3546
|
+
"subType": "HORIZONTAL",
|
|
3547
|
+
"children": [
|
|
3548
|
+
{
|
|
3549
|
+
"name": "startDate",
|
|
3550
|
+
"label": "Start Date",
|
|
3551
|
+
"type": "DATE",
|
|
3552
|
+
"subType": "SINGLE",
|
|
3553
|
+
"required": true
|
|
3554
|
+
},
|
|
3555
|
+
{
|
|
3556
|
+
"name": "endDate",
|
|
3557
|
+
"label": "End Date",
|
|
3558
|
+
"type": "DATE",
|
|
3559
|
+
"subType": "SINGLE"
|
|
3560
|
+
}
|
|
3561
|
+
]
|
|
3562
|
+
},
|
|
3563
|
+
{
|
|
3564
|
+
"name": "responsibilities",
|
|
3565
|
+
"label": "Key Responsibilities",
|
|
3566
|
+
"type": "TEXT_INPUT",
|
|
3567
|
+
"subType": "LONG"
|
|
3568
|
+
}
|
|
3569
|
+
]
|
|
3570
|
+
}
|
|
3571
|
+
},
|
|
3572
|
+
{
|
|
3573
|
+
"type": "GROUP",
|
|
3574
|
+
"subType": "SECTION",
|
|
3575
|
+
"sectionConfig": {
|
|
3576
|
+
"label": "Skills & Qualifications",
|
|
3577
|
+
"children": [
|
|
3578
|
+
{
|
|
3579
|
+
"name": "skills",
|
|
3580
|
+
"label": "Technical Skills",
|
|
3581
|
+
"type": "CHIP",
|
|
3582
|
+
"subType": "MULTIPLE",
|
|
3583
|
+
"optionConfig": {
|
|
3584
|
+
"optionList": [
|
|
3585
|
+
{ "label": "JavaScript", "code": "JS" },
|
|
3586
|
+
{ "label": "TypeScript", "code": "TS" },
|
|
3587
|
+
{ "label": "Angular", "code": "ANGULAR" },
|
|
3588
|
+
{ "label": "React", "code": "REACT" },
|
|
3589
|
+
{ "label": "Node.js", "code": "NODE" },
|
|
3590
|
+
{ "label": "Python", "code": "PYTHON" }
|
|
3591
|
+
]
|
|
3592
|
+
}
|
|
3593
|
+
},
|
|
3594
|
+
{
|
|
3595
|
+
"name": "yearsOfExperience",
|
|
3596
|
+
"label": "Years of Experience",
|
|
3597
|
+
"type": "NUMBER_INPUT",
|
|
3598
|
+
"subType": "INTEGER",
|
|
3599
|
+
"required": true,
|
|
3600
|
+
"numberConfig": {
|
|
3601
|
+
"min": 0,
|
|
3602
|
+
"max": 50
|
|
3603
|
+
}
|
|
3604
|
+
},
|
|
3605
|
+
{
|
|
3606
|
+
"name": "availableToStart",
|
|
3607
|
+
"label": "Available to Start",
|
|
3608
|
+
"type": "DATE",
|
|
3609
|
+
"subType": "SINGLE",
|
|
3610
|
+
"required": true,
|
|
3611
|
+
"dateConfig": {
|
|
3612
|
+
"allowFuture": true
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
]
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
],
|
|
3619
|
+
"showStep": true,
|
|
3620
|
+
"isHorizontal": true
|
|
3621
|
+
}
|
|
3622
|
+
}`
|
|
3623
|
+
};
|
|
3624
|
+
|
|
3625
|
+
var smartForm_examples = /*#__PURE__*/Object.freeze({
|
|
3626
|
+
__proto__: null,
|
|
3627
|
+
SAMPLE_FORMS: SAMPLE_FORMS
|
|
3628
|
+
});
|
|
3629
|
+
|
|
2036
3630
|
/*
|
|
2037
3631
|
* Public API Surface of shared-ui
|
|
2038
3632
|
*/
|
|
@@ -2041,5 +3635,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
|
|
|
2041
3635
|
* Generated bundle index. Do not edit.
|
|
2042
3636
|
*/
|
|
2043
3637
|
|
|
2044
|
-
export { AlertComponent, AlertModule, ButtonComponent, ButtonModule,
|
|
3638
|
+
export { AlertComponent, AlertModule, ButtonComponent, ButtonModule, ConfigurableFormComponent, configurableForm_examples as ConfigurableFormExamples, ConfigurableFormModule, ConfirmationModalComponent, ConfirmationModalModule, DEFAULT_ITEMS_PER_PAGE, DEFAULT_PAGE_SIZE_OPTIONS, ExpressionService, FilterSidebarComponent, FilterSidebarModule, MaterialModule, NAV_ORIENTATION_DEFAULT, NAV_VARIANT_DEFAULT, NavComponent, NavModule, PAGINATION_THEME_DARK, PAGINATION_THEME_DEFAULT, PaginationComponent, PaginationModule, SharedUiModule, SmartFormComponent, SmartFormController, smartForm_examples as SmartFormExamples, SmartFormModule, SmartTableComponent, SmartTableModule, SummaryCardComponent, SummaryCardModule, ValidationUtils, clearLocalStorage, clearSessionStorage, getLocalStorageItem, getSessionStorageItem, removeLocalStorageItem, removeSessionStorageItem, setLocalStorageItem, setSessionStorageItem };
|
|
2045
3639
|
//# sourceMappingURL=commons-shared-web-ui.mjs.map
|