@elite.framework/ng.ui.core 1.0.81 → 1.0.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/base-crud/index.d.ts +1 -0
- package/fesm2022/elite.framework-ng.ui.core-base-crud.mjs +17 -2
- package/fesm2022/elite.framework-ng.ui.core-base-crud.mjs.map +1 -1
- package/fesm2022/elite.framework-ng.ui.core-generic-crud-table.mjs +29 -29
- package/fesm2022/elite.framework-ng.ui.core-generic-crud-table.mjs.map +1 -1
- package/fesm2022/elite.framework-ng.ui.core-generic-report.mjs +354 -286
- package/fesm2022/elite.framework-ng.ui.core-generic-report.mjs.map +1 -1
- package/fesm2022/elite.framework-ng.ui.core-generic-search-advanced.mjs +1307 -123
- package/fesm2022/elite.framework-ng.ui.core-generic-search-advanced.mjs.map +1 -1
- package/fesm2022/elite.framework-ng.ui.core-generic-table.mjs +2 -2
- package/fesm2022/elite.framework-ng.ui.core-generic-table.mjs.map +1 -1
- package/generic-crud-table/index.d.ts +2 -6
- package/generic-report/index.d.ts +50 -26
- package/generic-search-advanced/index.d.ts +127 -25
- package/package.json +17 -17
|
@@ -2,8 +2,8 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { NgModule, inject, Injectable, EventEmitter, Input, Output, Component, ChangeDetectorRef } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule } from '@angular/common';
|
|
5
|
-
import * as
|
|
6
|
-
import { FormGroup, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
5
|
+
import * as i2$1 from '@angular/forms';
|
|
6
|
+
import { FormGroup, ReactiveFormsModule, FormsModule, FormControl, FormArray } from '@angular/forms';
|
|
7
7
|
import { FormlyForm, FieldType, FormlyField, provideFormlyConfig } from '@ngx-formly/core';
|
|
8
8
|
import * as i10 from '@ngx-translate/core';
|
|
9
9
|
import { TranslateService, TranslateModule } from '@ngx-translate/core';
|
|
@@ -32,13 +32,14 @@ import * as i6$1 from 'primeng/tooltip';
|
|
|
32
32
|
import { TooltipModule } from 'primeng/tooltip';
|
|
33
33
|
import * as i7$1 from 'primeng/listbox';
|
|
34
34
|
import { ListboxModule } from 'primeng/listbox';
|
|
35
|
+
import * as i4$1 from 'primeng/checkbox';
|
|
35
36
|
import { CheckboxModule } from 'primeng/checkbox';
|
|
36
37
|
import { DatePickerModule } from 'primeng/datepicker';
|
|
37
38
|
import { RadioButtonModule } from 'primeng/radiobutton';
|
|
38
39
|
import { MenuModule } from 'primeng/menu';
|
|
39
40
|
import * as i6$2 from 'primeng/popover';
|
|
40
41
|
import { PopoverModule } from 'primeng/popover';
|
|
41
|
-
import * as i2$
|
|
42
|
+
import * as i2$2 from 'primeng/tabs';
|
|
42
43
|
import { TabsModule } from 'primeng/tabs';
|
|
43
44
|
|
|
44
45
|
class GenericSearchAdvancedModule {
|
|
@@ -68,36 +69,27 @@ class QueryBuilderService {
|
|
|
68
69
|
'radio': { defaultOperator: 'eq', defaultValue: '', operators: ['eq', 'ne'] },
|
|
69
70
|
'textarea': { defaultOperator: 'contains', defaultValue: '', operators: ['eq', 'ne', 'contains', 'startswith', 'endswith'] }
|
|
70
71
|
};
|
|
71
|
-
buildQueryUIFields(fields) {
|
|
72
|
+
buildQueryUIFields(fields, enableSelect, enableGroup) {
|
|
72
73
|
return [
|
|
73
|
-
// {
|
|
74
|
-
// key: 'quickSearch',
|
|
75
|
-
// type: 'input',
|
|
76
|
-
// props: {
|
|
77
|
-
// label: 'بحث سريع',
|
|
78
|
-
// placeholder: 'ابحث في جميع الحقول...',
|
|
79
|
-
// icon: 'pi pi-search'
|
|
80
|
-
// }
|
|
81
|
-
// },
|
|
82
74
|
{
|
|
83
75
|
type: 'tab-type',
|
|
84
|
-
// key: 'queryTabs',
|
|
85
76
|
props: {
|
|
86
77
|
tabViewClass: 'advanced-query-tabs',
|
|
87
78
|
scrollable: false,
|
|
88
79
|
lazy: true,
|
|
89
80
|
activeIndex: 0
|
|
90
81
|
},
|
|
91
|
-
fieldGroup: this.getQueryTabs(fields)
|
|
82
|
+
fieldGroup: this.getQueryTabs(fields, enableSelect, enableGroup)
|
|
92
83
|
}
|
|
93
84
|
];
|
|
94
85
|
}
|
|
95
|
-
getQueryTabs(fields) {
|
|
96
|
-
|
|
86
|
+
getQueryTabs(fields, enableSelect, enableGroup) {
|
|
87
|
+
const tabs = [
|
|
97
88
|
{
|
|
98
89
|
type: 'group',
|
|
99
90
|
props: {
|
|
100
|
-
|
|
91
|
+
tabLabel: 'FILTERS',
|
|
92
|
+
// label: 'FILTERS',
|
|
101
93
|
leftIcon: 'pi pi-filter',
|
|
102
94
|
badge: (field) => {
|
|
103
95
|
const fieldGroup = (field?.fieldGroup ?? [])[0];
|
|
@@ -109,7 +101,8 @@ class QueryBuilderService {
|
|
|
109
101
|
{
|
|
110
102
|
type: 'group',
|
|
111
103
|
props: {
|
|
112
|
-
|
|
104
|
+
tabLabel: 'SORTING',
|
|
105
|
+
// label: 'SORTING',
|
|
113
106
|
leftIcon: 'pi pi-sort-alt',
|
|
114
107
|
badge: (field) => {
|
|
115
108
|
const fieldGroup = (field?.fieldGroup ?? [])[0];
|
|
@@ -118,11 +111,47 @@ class QueryBuilderService {
|
|
|
118
111
|
},
|
|
119
112
|
fieldGroup: this.getSortingTabFields(fields)
|
|
120
113
|
},
|
|
121
|
-
{
|
|
114
|
+
enableGroup && {
|
|
122
115
|
type: 'group',
|
|
123
116
|
props: {
|
|
124
|
-
|
|
117
|
+
tabLabel: 'GROUPING',
|
|
118
|
+
// label: 'GROUPING',
|
|
125
119
|
leftIcon: 'pi pi-table',
|
|
120
|
+
badge: (field) => {
|
|
121
|
+
const fieldGroup = (field?.fieldGroup ?? [])[0];
|
|
122
|
+
return (fieldGroup?.model?.grouping ?? [])?.length || 0;
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
fieldGroup: this.getGroupingTabFields(fields)
|
|
126
|
+
},
|
|
127
|
+
enableSelect && {
|
|
128
|
+
type: 'group',
|
|
129
|
+
props: {
|
|
130
|
+
tabLabel: 'COLUMNS',
|
|
131
|
+
// label: 'COLUMNS',
|
|
132
|
+
leftIcon: 'pi pi-list',
|
|
133
|
+
badge: (field) => {
|
|
134
|
+
const fieldGroup = (field?.fieldGroup ?? [])[0];
|
|
135
|
+
const columns = fieldGroup?.model?.columns;
|
|
136
|
+
// Handle both array and object formats for columns
|
|
137
|
+
if (Array.isArray(columns)) {
|
|
138
|
+
return columns.filter((col) => col?.isVisible)?.length || 0;
|
|
139
|
+
}
|
|
140
|
+
else if (columns && typeof columns === 'object') {
|
|
141
|
+
// If columns is an object, count the visible ones
|
|
142
|
+
return Object.values(columns).filter((col) => col?.isVisible)?.length || 0;
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
fieldGroup: this.getColumnsTabFields(fields)
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: 'group',
|
|
151
|
+
props: {
|
|
152
|
+
tabLabel: 'PAGINATION',
|
|
153
|
+
// label: 'PAGINATION',
|
|
154
|
+
leftIcon: 'pi pi-cog',
|
|
126
155
|
badge: (field) => {
|
|
127
156
|
return field?.model?.pagination && field?.model?.pagination?.top ? 1 : 0;
|
|
128
157
|
},
|
|
@@ -130,6 +159,7 @@ class QueryBuilderService {
|
|
|
130
159
|
fieldGroup: this.getPaginationTabFields()
|
|
131
160
|
}
|
|
132
161
|
];
|
|
162
|
+
return tabs.filter(Boolean);
|
|
133
163
|
}
|
|
134
164
|
getFilterTabFields(fields) {
|
|
135
165
|
return [
|
|
@@ -137,8 +167,8 @@ class QueryBuilderService {
|
|
|
137
167
|
key: 'advancedFilters',
|
|
138
168
|
type: 'query-builder',
|
|
139
169
|
props: {
|
|
140
|
-
label: '
|
|
141
|
-
description: 'أنشئ
|
|
170
|
+
label: 'فلاتر متقدمة',
|
|
171
|
+
description: 'أنشئ فلاتر معقدة باستخدام مشغلين منطقيين',
|
|
142
172
|
fields: this.extractFilterableFields(fields)
|
|
143
173
|
}
|
|
144
174
|
}
|
|
@@ -157,6 +187,33 @@ class QueryBuilderService {
|
|
|
157
187
|
}
|
|
158
188
|
];
|
|
159
189
|
}
|
|
190
|
+
getGroupingTabFields(fields) {
|
|
191
|
+
return [
|
|
192
|
+
{
|
|
193
|
+
key: 'grouping',
|
|
194
|
+
type: 'group-builder',
|
|
195
|
+
props: {
|
|
196
|
+
label: 'تجميع البيانات',
|
|
197
|
+
description: 'قم بتجميع البيانات حسب الحقول المطلوبة',
|
|
198
|
+
fields: this.extractGroupableFields(fields),
|
|
199
|
+
maxGroups: 3
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
getColumnsTabFields(fields) {
|
|
205
|
+
return [
|
|
206
|
+
{
|
|
207
|
+
key: 'columns',
|
|
208
|
+
type: 'columns-builder',
|
|
209
|
+
props: {
|
|
210
|
+
label: 'إدارة الأعمدة',
|
|
211
|
+
description: 'اختر الحقول المراد عرضها وتخصيص التجميع',
|
|
212
|
+
fields: this.extractSelectableFields(fields)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
}
|
|
160
217
|
getPaginationTabFields() {
|
|
161
218
|
return [
|
|
162
219
|
{
|
|
@@ -181,36 +238,40 @@ class QueryBuilderService {
|
|
|
181
238
|
description: 'أقصى عدد للسجلات المراد عرضها'
|
|
182
239
|
}
|
|
183
240
|
},
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
241
|
+
{
|
|
242
|
+
key: 'skip',
|
|
243
|
+
type: 'input',
|
|
244
|
+
props: {
|
|
245
|
+
type: 'number',
|
|
246
|
+
label: 'تخطي السجلات',
|
|
247
|
+
min: 0,
|
|
248
|
+
description: 'عدد السجلات التي سيتم تخطيها'
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
key: 'includeCount',
|
|
253
|
+
type: 'checkbox',
|
|
254
|
+
props: {
|
|
255
|
+
label: 'تضمين العدد الإجمالي',
|
|
256
|
+
description: 'تضمين عدد السجلات الإجمالي في الاستجابة'
|
|
257
|
+
}
|
|
258
|
+
}
|
|
194
259
|
]
|
|
195
260
|
}
|
|
196
261
|
];
|
|
197
262
|
}
|
|
198
|
-
getFilterCount() {
|
|
199
|
-
return '0';
|
|
200
|
-
}
|
|
201
263
|
extractFilterableFields(fields) {
|
|
202
264
|
const filterableFields = [];
|
|
203
265
|
this.traverseFields(fields, (field) => {
|
|
204
|
-
if (field.key && field.props && field.props['
|
|
266
|
+
if (field.key && field.props && ((field.props['filter'] && field.props['filter'].hidden == false) || !field.props['filter']) && !field.props?.hidden) {
|
|
205
267
|
const fieldType = this.mapFieldType(field.type);
|
|
206
268
|
const fieldConfig = this.fieldTypeConfig[fieldType] || this.fieldTypeConfig.input;
|
|
207
269
|
filterableFields.push({
|
|
208
|
-
key: field.key,
|
|
270
|
+
key: field.props['entityKey'] ? field.props['entityKey'] : field.key,
|
|
209
271
|
label: this.translate.instant(field.props.label || field.key),
|
|
210
272
|
type: fieldType,
|
|
211
273
|
props: { ...field.props },
|
|
212
274
|
operators: fieldConfig.operators,
|
|
213
|
-
// Add default values for QueryBuilderComponent compatibility
|
|
214
275
|
defaultOperator: fieldConfig.defaultOperator,
|
|
215
276
|
defaultValue: fieldConfig.defaultValue
|
|
216
277
|
});
|
|
@@ -221,26 +282,44 @@ class QueryBuilderService {
|
|
|
221
282
|
extractSortableFields(fields) {
|
|
222
283
|
const sortableFields = [];
|
|
223
284
|
this.traverseFields(fields, (field) => {
|
|
224
|
-
if (field.key && field.props && field.props['
|
|
285
|
+
if (field.key && field.props && ((field.props['sort'] && field.props['sort'].hidden == false) || !field.props['sort']) && !field.props?.hidden) {
|
|
225
286
|
sortableFields.push({
|
|
226
|
-
key: field.key,
|
|
287
|
+
key: field.props['entityKey'] ? field.props['entityKey'] : field.key,
|
|
227
288
|
label: this.translate.instant(field.props.label || field.key)
|
|
228
289
|
});
|
|
229
290
|
}
|
|
230
291
|
});
|
|
231
292
|
return sortableFields;
|
|
232
293
|
}
|
|
233
|
-
|
|
234
|
-
const
|
|
294
|
+
extractGroupableFields(fields) {
|
|
295
|
+
const groupableFields = [];
|
|
235
296
|
this.traverseFields(fields, (field) => {
|
|
236
|
-
if (field.key && field.props && field.props['
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
297
|
+
if (field.key && field.props && ((field.props['group'] && field.props['group'].hidden == false) || !field.props['group']) && !field.props?.hidden) {
|
|
298
|
+
groupableFields.push({
|
|
299
|
+
key: field.props['entityKey'] ? field.props['entityKey'] : field.key,
|
|
300
|
+
label: this.translate.instant(field.props.label || field.key),
|
|
301
|
+
dataType: this.getDataType(field.type)
|
|
240
302
|
});
|
|
241
303
|
}
|
|
242
304
|
});
|
|
243
|
-
return
|
|
305
|
+
return groupableFields;
|
|
306
|
+
}
|
|
307
|
+
extractSelectableFields(fields) {
|
|
308
|
+
const selectableFields = [];
|
|
309
|
+
this.traverseFields(fields, (field) => {
|
|
310
|
+
if (field.key && field.props && ((field.props['select'] && field.props['select'].hidden == false) || !field.props['select']) && !field.props?.hidden) {
|
|
311
|
+
const isNumeric = ['number', 'currency'].includes(field.type);
|
|
312
|
+
selectableFields.push({
|
|
313
|
+
key: field.props['entityKey'] ? field.props['entityKey'] : field.key,
|
|
314
|
+
label: this.translate.instant(field.props.label || field.key),
|
|
315
|
+
dataType: this.getDataType(field.type),
|
|
316
|
+
isNumeric: isNumeric,
|
|
317
|
+
defaultVisible: field.props['defaultVisible'] !== false,
|
|
318
|
+
aggregatable: isNumeric && field.props['aggregatable'] !== false
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
return selectableFields;
|
|
244
323
|
}
|
|
245
324
|
traverseFields(fields, callback) {
|
|
246
325
|
fields.forEach(field => {
|
|
@@ -250,6 +329,58 @@ class QueryBuilderService {
|
|
|
250
329
|
}
|
|
251
330
|
});
|
|
252
331
|
}
|
|
332
|
+
getAppliedFiltersCount(model) {
|
|
333
|
+
const breakdown = {
|
|
334
|
+
filters: 0,
|
|
335
|
+
sorting: 0,
|
|
336
|
+
grouping: 0,
|
|
337
|
+
columns: 0,
|
|
338
|
+
pagination: 0,
|
|
339
|
+
expand: 0
|
|
340
|
+
};
|
|
341
|
+
// Advanced filters
|
|
342
|
+
if (model?.advancedFilters && Array.isArray(model.advancedFilters)) {
|
|
343
|
+
model.advancedFilters.forEach((filterGroup) => {
|
|
344
|
+
if (filterGroup.conditions && Array.isArray(filterGroup.conditions)) {
|
|
345
|
+
breakdown.filters += filterGroup.conditions.length;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
// Sorting
|
|
350
|
+
if (model?.sorting && Array.isArray(model.sorting)) {
|
|
351
|
+
breakdown.sorting = model.sorting.length;
|
|
352
|
+
}
|
|
353
|
+
// Grouping
|
|
354
|
+
if (model?.grouping && Array.isArray(model.grouping)) {
|
|
355
|
+
breakdown.grouping = model.grouping.length;
|
|
356
|
+
}
|
|
357
|
+
// Columns
|
|
358
|
+
if (model?.columns) {
|
|
359
|
+
if (Array.isArray(model.columns)) {
|
|
360
|
+
breakdown.columns = model.columns.filter((col) => col.isVisible).length;
|
|
361
|
+
}
|
|
362
|
+
else if (typeof model.columns === 'object') {
|
|
363
|
+
breakdown.columns = Object.values(model.columns).filter((col) => col?.isVisible).length;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// Pagination
|
|
367
|
+
if (model?.pagination) {
|
|
368
|
+
if (model.pagination.top)
|
|
369
|
+
breakdown.pagination += 1;
|
|
370
|
+
if (model.pagination.skip)
|
|
371
|
+
breakdown.pagination += 1;
|
|
372
|
+
if (model.pagination.includeCount)
|
|
373
|
+
breakdown.pagination += 1;
|
|
374
|
+
}
|
|
375
|
+
// Expand
|
|
376
|
+
if (model?.expand && Array.isArray(model.expand)) {
|
|
377
|
+
breakdown.expand = model.expand.length;
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
total: Object.values(breakdown).reduce((sum, count) => sum + count, 0),
|
|
381
|
+
breakdown
|
|
382
|
+
};
|
|
383
|
+
}
|
|
253
384
|
mapFieldType(type) {
|
|
254
385
|
const typeMap = {
|
|
255
386
|
'input': 'input',
|
|
@@ -260,30 +391,49 @@ class QueryBuilderService {
|
|
|
260
391
|
'generic-selector': 'generic-selector',
|
|
261
392
|
'switch': 'switch',
|
|
262
393
|
'radio': 'radio',
|
|
263
|
-
'textarea': 'textarea'
|
|
394
|
+
'textarea': 'textarea',
|
|
395
|
+
'currency': 'number'
|
|
264
396
|
};
|
|
265
397
|
return typeMap[type || 'input'] || 'input';
|
|
266
398
|
}
|
|
399
|
+
getDataType(type) {
|
|
400
|
+
const dataTypeMap = {
|
|
401
|
+
'input': 'string',
|
|
402
|
+
'number': 'number',
|
|
403
|
+
'datepicker': 'date',
|
|
404
|
+
'select': 'string',
|
|
405
|
+
'checkbox': 'boolean',
|
|
406
|
+
'generic-selector': 'string',
|
|
407
|
+
'switch': 'boolean',
|
|
408
|
+
'radio': 'string',
|
|
409
|
+
'textarea': 'string',
|
|
410
|
+
'currency': 'number'
|
|
411
|
+
};
|
|
412
|
+
return dataTypeMap[type || 'input'] || 'string';
|
|
413
|
+
}
|
|
267
414
|
getOperatorsForType(type) {
|
|
268
415
|
const fieldType = this.mapFieldType(type);
|
|
269
416
|
const config = this.fieldTypeConfig[fieldType];
|
|
270
417
|
return config ? config.operators : ['eq', 'ne'];
|
|
271
418
|
}
|
|
272
|
-
// OData methods
|
|
419
|
+
// Enhanced OData methods with GroupBy and Select support
|
|
273
420
|
buildODataFromQueryModel(model, originalFields) {
|
|
274
421
|
const params = {
|
|
275
422
|
filters: [],
|
|
276
423
|
orderBy: [],
|
|
277
424
|
groupBy: [],
|
|
278
|
-
|
|
425
|
+
select: [],
|
|
426
|
+
aggregates: [],
|
|
427
|
+
expand: model.expand || [],
|
|
428
|
+
includeCount: model.pagination?.includeCount || false
|
|
279
429
|
};
|
|
280
430
|
// Quick search
|
|
281
|
-
if (model.
|
|
282
|
-
const searchableFields = this.
|
|
431
|
+
if (model.filter) {
|
|
432
|
+
const searchableFields = this.extractSelectableFields(originalFields); // TODO get all strings filters
|
|
283
433
|
const quickSearchConditions = searchableFields.map(field => ({
|
|
284
434
|
field: field.key,
|
|
285
435
|
operator: 'contains',
|
|
286
|
-
value: model.
|
|
436
|
+
value: model.filter
|
|
287
437
|
}));
|
|
288
438
|
if (quickSearchConditions.length > 0) {
|
|
289
439
|
params.filters.push({
|
|
@@ -305,6 +455,80 @@ class QueryBuilderService {
|
|
|
305
455
|
direction: sort.direction
|
|
306
456
|
}));
|
|
307
457
|
}
|
|
458
|
+
// Grouping
|
|
459
|
+
if (model.grouping && Array.isArray(model.grouping)) {
|
|
460
|
+
params.groupBy = model.grouping
|
|
461
|
+
.filter((group) => group.field)
|
|
462
|
+
.map((group, index) => ({
|
|
463
|
+
propertyName: group.field,
|
|
464
|
+
displayName: group.displayName || group.field,
|
|
465
|
+
showTotal: group.showTotal !== false,
|
|
466
|
+
order: index
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
// Columns/Select - Handle both array and object formats
|
|
470
|
+
// extractSelectableFields
|
|
471
|
+
if (model.columns) {
|
|
472
|
+
let visibleColumns = [];
|
|
473
|
+
// Handle array format: [{field: 'name', isVisible: true, ...}, ...]
|
|
474
|
+
if (Array.isArray(model.columns)) {
|
|
475
|
+
visibleColumns = model.columns.filter((col) => col.isVisible);
|
|
476
|
+
}
|
|
477
|
+
// Handle object format: {field1: {isVisible: true, displayName: 'Name', ...}, field2: {...}}
|
|
478
|
+
else if (typeof model.columns === 'object') {
|
|
479
|
+
visibleColumns = Object.entries(model.columns)
|
|
480
|
+
.filter(([key, col]) => col.isVisible)
|
|
481
|
+
.map(([key, col]) => ({
|
|
482
|
+
field: key, // Use the object key as field name
|
|
483
|
+
...col // Spread the column properties
|
|
484
|
+
}));
|
|
485
|
+
}
|
|
486
|
+
// Process visible columns for select and aggregates
|
|
487
|
+
if (visibleColumns.length > 0) {
|
|
488
|
+
params.select = visibleColumns.map((col, index) => ({
|
|
489
|
+
propertyName: col.field || col.key, // Support both 'field' and 'key' properties
|
|
490
|
+
displayName: col.displayName || col.field || col.key || col.label,
|
|
491
|
+
aggregateFunction: col.aggregateFunction,
|
|
492
|
+
isVisible: true,
|
|
493
|
+
order: index
|
|
494
|
+
}));
|
|
495
|
+
// Extract aggregates from columns with aggregate functions
|
|
496
|
+
const aggregateColumns = visibleColumns.filter((col) => col.aggregateFunction);
|
|
497
|
+
params.aggregates = aggregateColumns.map((col) => ({
|
|
498
|
+
propertyName: col.field || col.key,
|
|
499
|
+
aggregateFunction: col.aggregateFunction,
|
|
500
|
+
alias: `${col.aggregateFunction}_${col.field || col.key}`
|
|
501
|
+
}));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
// Fallback: auto-generate from formly fields
|
|
506
|
+
const selectableFields = this.extractSelectableFields(originalFields);
|
|
507
|
+
// Ensure a column for the ID field exists
|
|
508
|
+
// if (!selectableFields.some(f => f.key === this.idField)) {
|
|
509
|
+
// selectableFields.unshift({
|
|
510
|
+
// key: this.idField,
|
|
511
|
+
// label: this.translate.instant(this.idField),
|
|
512
|
+
// dataType: 'number', // or the correct type
|
|
513
|
+
// isNumeric: false,
|
|
514
|
+
// defaultVisible: true,
|
|
515
|
+
// aggregatable: false
|
|
516
|
+
// });
|
|
517
|
+
// }
|
|
518
|
+
params.select = selectableFields.map((f, i) => ({
|
|
519
|
+
propertyName: f.key,
|
|
520
|
+
displayName: f.label,
|
|
521
|
+
isVisible: f.defaultVisible,
|
|
522
|
+
order: i
|
|
523
|
+
}));
|
|
524
|
+
params.aggregates = selectableFields
|
|
525
|
+
.filter(f => f.aggregatable)
|
|
526
|
+
.map(f => ({
|
|
527
|
+
propertyName: f.key,
|
|
528
|
+
aggregateFunction: 'sum',
|
|
529
|
+
alias: `sum_${f.key}`
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
308
532
|
// Pagination
|
|
309
533
|
if (model.pagination) {
|
|
310
534
|
if (model.pagination.top) {
|
|
@@ -313,6 +537,9 @@ class QueryBuilderService {
|
|
|
313
537
|
if (model.pagination.skip) {
|
|
314
538
|
params.skip = Number(model.pagination.skip);
|
|
315
539
|
}
|
|
540
|
+
if (model.pagination.includeCount !== undefined) {
|
|
541
|
+
params.includeCount = model.pagination.includeCount;
|
|
542
|
+
}
|
|
316
543
|
}
|
|
317
544
|
return params;
|
|
318
545
|
}
|
|
@@ -322,60 +549,20 @@ class QueryBuilderService {
|
|
|
322
549
|
parseODataQuery(queryString) {
|
|
323
550
|
return QueryParser.parse(queryString);
|
|
324
551
|
}
|
|
325
|
-
|
|
326
|
-
const params = this.parseODataQuery(queryString);
|
|
327
|
-
const model = {
|
|
328
|
-
advancedFilters: [],
|
|
329
|
-
sorting: [],
|
|
330
|
-
pagination: {}
|
|
331
|
-
};
|
|
332
|
-
// Parse filters back to UI model
|
|
333
|
-
if (params.filters && params.filters.length > 0) {
|
|
334
|
-
params.filters.forEach((group) => {
|
|
335
|
-
if (group.logicalOperator === 'or' && group.conditions.length > 1) {
|
|
336
|
-
const firstValue = group.conditions[0].value;
|
|
337
|
-
if (group.conditions.every(cond => cond.operator === 'contains' && cond.value === firstValue)) {
|
|
338
|
-
model.quickSearch = firstValue;
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
group.conditions.forEach(condition => {
|
|
343
|
-
model.advancedFilters.push({
|
|
344
|
-
field: condition.field,
|
|
345
|
-
operator: condition.operator,
|
|
346
|
-
value: condition.value
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
// Parse sorting
|
|
352
|
-
if (params.orderBy) {
|
|
353
|
-
model.sorting = params.orderBy.map(order => ({
|
|
354
|
-
field: order.field,
|
|
355
|
-
direction: order.direction
|
|
356
|
-
}));
|
|
357
|
-
}
|
|
358
|
-
// Parse pagination
|
|
359
|
-
if (params.top !== undefined) {
|
|
360
|
-
model.pagination.top = params.top;
|
|
361
|
-
}
|
|
362
|
-
if (params.skip !== undefined) {
|
|
363
|
-
model.pagination.skip = params.skip;
|
|
364
|
-
}
|
|
365
|
-
// Parse expand
|
|
366
|
-
if (params.expand) {
|
|
367
|
-
model.expand = params.expand;
|
|
368
|
-
}
|
|
369
|
-
return model;
|
|
370
|
-
}
|
|
371
|
-
buildODataQueryString(model, originalFields) {
|
|
372
|
-
const params = this.buildODataFromQueryModel(model, originalFields);
|
|
373
|
-
return this.toODataQueryString(params);
|
|
374
|
-
}
|
|
375
|
-
// Helper method to get field configuration for QueryBuilderComponent
|
|
552
|
+
// Helper method to get field configuration
|
|
376
553
|
getFieldConfig(fieldKey, fields) {
|
|
377
554
|
return fields.find(f => f.key === fieldKey);
|
|
378
555
|
}
|
|
556
|
+
// Helper to get available aggregate functions for numeric fields
|
|
557
|
+
getAggregateFunctions() {
|
|
558
|
+
return [
|
|
559
|
+
{ label: 'مجموع', value: 'sum' },
|
|
560
|
+
{ label: 'متوسط', value: 'avg' },
|
|
561
|
+
{ label: 'عدد', value: 'count' },
|
|
562
|
+
{ label: 'الحد الأدنى', value: 'min' },
|
|
563
|
+
{ label: 'الحد الأقصى', value: 'max' }
|
|
564
|
+
];
|
|
565
|
+
}
|
|
379
566
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: QueryBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
380
567
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: QueryBuilderService, providedIn: 'root' });
|
|
381
568
|
}
|
|
@@ -399,7 +586,8 @@ class GenericSearchAdvanced {
|
|
|
399
586
|
options = {};
|
|
400
587
|
fields = [];
|
|
401
588
|
fields_ = [];
|
|
402
|
-
|
|
589
|
+
enableSelect = false;
|
|
590
|
+
enableGroup = false;
|
|
403
591
|
odataConfig = {};
|
|
404
592
|
ngOnInit() {
|
|
405
593
|
this.buildQueryUIFields();
|
|
@@ -411,12 +599,7 @@ class GenericSearchAdvanced {
|
|
|
411
599
|
return filters;
|
|
412
600
|
}
|
|
413
601
|
buildQueryUIFields() {
|
|
414
|
-
|
|
415
|
-
this.fields_ = this.queryBuilder.buildQueryUIFields(this.fields);
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
this.fields_ = this.fields;
|
|
419
|
-
}
|
|
602
|
+
this.fields_ = this.queryBuilder.buildQueryUIFields(this.fields, this.enableSelect, this.enableGroup);
|
|
420
603
|
}
|
|
421
604
|
onSubmit() {
|
|
422
605
|
// Use the new QueryBuilderService methods
|
|
@@ -465,7 +648,7 @@ class GenericSearchAdvanced {
|
|
|
465
648
|
this.drawerVisible = false;
|
|
466
649
|
}
|
|
467
650
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GenericSearchAdvanced, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
468
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.8", type: GenericSearchAdvanced, isStandalone: true, selector: "lib-generic-search-advanced", inputs: { model: "model", fields: "fields",
|
|
651
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.8", type: GenericSearchAdvanced, isStandalone: true, selector: "lib-generic-search-advanced", inputs: { model: "model", fields: "fields", enableSelect: "enableSelect", enableGroup: "enableGroup", odataConfig: "odataConfig" }, outputs: { search: "search", paginationChange: "paginationChange", odataSearch: "odataSearch" }, ngImport: i0, template: "<div class=\"flex flex-wrap items-center gap-3 w-full\">\r\n <!-- Search Input with Icon -->\r\n <p-inputgroup class=\"w-full\">\r\n <input\r\n pInputText\r\n type=\"text\"\r\n [(ngModel)]=\"model['filter']\"\r\n placeholder=\"{{ 'SEARCH' | translate }}\"\r\n (keyup.enter)=\"onSubmit()\"\r\n />\r\n\r\n\r\n <p-inputgroup-addon>\r\n <p-button\r\n icon=\"pi pi-search\"\r\n severity=\"secondary\"\r\n (click)=\"onSubmit()\"\r\n ></p-button>\r\n </p-inputgroup-addon>\r\n<p-inputgroup-addon>\r\n <div class=\"relative inline-flex\">\r\n <p-button\r\n icon=\"pi pi-filter\"\r\n severity=\"secondary\"\r\n (onClick)=\"drawerVisible = true\">\r\n </p-button>\r\n\r\n <p-badge\r\n *ngIf=\"filterCount > 0\"\r\n [value]=\"filterCount\"\r\n severity=\"warn\"\r\n size=\"small\"\r\n class=\"absolute -top-0 -right-0\">\r\n </p-badge>\r\n </div>\r\n</p-inputgroup-addon>\r\n\r\n <p-inputgroup-addon *ngIf=\"model['filter'] || filterCount > 0\">\r\n <p-button\r\n icon=\"pi pi-times\"\r\n severity=\"danger\"\r\n (click)=\"model['filter']=''; onReset()\"\r\n ></p-button>\r\n </p-inputgroup-addon>\r\n</p-inputgroup>\r\n\r\n\r\n</div>\r\n\r\n<!-- Drawer for Advanced Multi-field Search -->\r\n @if(drawerVisible){\r\n<p-drawer\r\n [(visible)]=\"drawerVisible\"\r\n position=\"right\"\r\n [styleClass]=\"'!w-full md:!w-80 lg:!w-[40rem] !h-full' \"\r\n [modal]=\"true\"\r\n [dismissible]=\"true\"\r\n styleClass=\"p-4 w-full max-w-md max-h-[90vh] flex flex-col\"\r\n>\r\n <!-- Entire form wrapper -->\r\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit(); drawerVisible=false\" class=\"flex flex-col flex-1\">\r\n <!-- Scrollable Form -->\r\n <div class=\"flex-1 overflow-auto\">\r\n <formly-form\r\n [form]=\"form\"\r\n [fields]=\"fields_\"\r\n [model]=\"model\"\r\n [options]=\"options\"\r\n >\r\n </formly-form>\r\n </div>\r\n\r\n </form>\r\n <!-- Action Buttons -->\r\n <ng-template pTemplate=\"footer\">\r\n <div class=\"flex justify-end mt-2 space-x-2 flex-none\">\r\n <button\r\n type=\"submit\"\r\n pButton size=\"small\"\r\n (click)=\" onSubmit(); drawerVisible=false\"\r\n label=\"{{ 'SEARCH' | translate }}\">\r\n </button>\r\n <button\r\n type=\"button\"\r\n pButton\r\n size=\"small\"\r\n class=\"p-button-text\"\r\n (click)=\"onReset(); drawerVisible=false\"\r\n >\r\n {{ 'CLEAR' | translate }}\r\n </button>\r\n </div>\r\n </ng-template>\r\n</p-drawer>\r\n\r\n }\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: ToolbarModule }, { kind: "directive", type: i2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: InputIconModule }, { kind: "ngmodule", type: IconFieldModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i5.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i6.Drawer, selector: "p-drawer", inputs: ["appendTo", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: InputGroupModule }, { kind: "component", type: i7.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "component", type: i8.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i9.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
|
|
469
652
|
}
|
|
470
653
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GenericSearchAdvanced, decorators: [{
|
|
471
654
|
type: Component,
|
|
@@ -495,7 +678,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImpor
|
|
|
495
678
|
type: Input
|
|
496
679
|
}], fields: [{
|
|
497
680
|
type: Input
|
|
498
|
-
}],
|
|
681
|
+
}], enableSelect: [{
|
|
682
|
+
type: Input
|
|
683
|
+
}], enableGroup: [{
|
|
499
684
|
type: Input
|
|
500
685
|
}], odataConfig: [{
|
|
501
686
|
type: Input
|
|
@@ -774,7 +959,7 @@ class SortBuilderComponent extends FieldType {
|
|
|
774
959
|
</div>
|
|
775
960
|
</div>
|
|
776
961
|
</div>
|
|
777
|
-
`, isInline: true, styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "ngmodule", type: FormsModule }, { kind: "directive", type:
|
|
962
|
+
`, isInline: true, styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { 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: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5$1.Draggable, selector: "[pDraggable]", inputs: ["pDraggable", "dragEffect", "dragHandle", "pDraggableDisabled"], outputs: ["onDragStart", "onDragEnd", "onDrag"] }, { kind: "directive", type: i5$1.Droppable, selector: "[pDroppable]", inputs: ["pDroppable", "pDroppableDisabled", "dropEffect"], outputs: ["onDragEnter", "onDragLeave", "onDrop"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }] });
|
|
778
963
|
}
|
|
779
964
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: SortBuilderComponent, decorators: [{
|
|
780
965
|
type: Component,
|
|
@@ -1252,7 +1437,7 @@ class QueryBuilderComponent extends FieldType {
|
|
|
1252
1437
|
}
|
|
1253
1438
|
}
|
|
1254
1439
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: QueryBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1255
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.8", type: QueryBuilderComponent, isStandalone: true, selector: "formly-query-builder", usesInheritance: true, ngImport: i0, template: "<div>\r\n\r\n <!-- Groups Container with Connecting Lines -->\r\n <div class=\"groups-container\" #groupsContainer>\r\n <div *ngFor=\"let group of groups; let groupIndex = index\"\r\n class=\"group-wrapper relative\"\r\n #groupElement>\r\n\r\n <!-- Vertical Connector Line from Previous Group -->\r\n<div *ngIf=\"groupIndex > 0\" class=\"vertical-connector\">\r\n <div class=\"vertical-line\"></div>\r\n\r\n <!-- Operator Button -->\r\n <button\r\n type=\"button\"\r\n class=\"group-logical-operator-box\"\r\n [class.and]=\"group.groupLogicalOperator === 'and'\"\r\n [class.or]=\"group.groupLogicalOperator === 'or'\"\r\n (click)=\"opPopover.toggle($event)\">\r\n <span class=\"operator-text\">\r\n {{ getLogicalOperatorText(group.groupLogicalOperator || 'and') }}\r\n </span>\r\n <div class=\"connector-arrow\"></div>\r\n </button>\r\n\r\n <!-- Popover -->\r\n <p-popover #opPopover appendTo=\"body\">\r\n <div class=\"w-12rem\">\r\n <p-listbox\r\n [options]=\"logicalOperators\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n [(ngModel)]=\"group.groupLogicalOperator\"\r\n (onChange)=\" opPopover.toggle($event)\"\r\n [style]=\"{ width: '100%' }\">\r\n </p-listbox>\r\n </div>\r\n </p-popover>\r\n</div>\r\n\r\n <div class=\"group-container mb-6 p-1 bg-white rounded-lg border-l-4 border-t-1 border-b-1 border-r-1 shadow-sm transition-all duration-300 relative\"\r\n [class.border-blue-500]=\"group.logicalOperator === 'and'\"\r\n [class.border-or-500]=\"group.logicalOperator === 'or'\"\r\n [class.pulse]=\"groupIndex === 0\">\r\n\r\n <!-- Rounded Button for \"\u0631\u0628\u0637 \u0627\u0644\u0634\u0631\u0648\u0637\" on left border -->\r\n<div\r\n class=\"absolute -left-4 top-1/2 transform -translate-y-1/2 z-10 transition-all duration-300\"\r\n\r\n>\r\n\r\n\r\n\r\n<!-- Logical Operator Popover Button -->\r\n<p-button\r\n [severity]=\"group.logicalOperator === 'and' ? 'success' : 'info'\"\r\n [styleClass]=\"group.logicalOperator === 'and'\r\n ? 'p-button-icon-only p-button-rounded p-button-sm logical-and'\r\n : 'p-button-icon-only p-button-rounded p-button-sm logical-or'\"\r\n (click)=\"opPopover.toggle($event)\">\r\n {{group.logicalOperator === 'and' ? ('and' | translate) : ('or' | translate)}}\r\n</p-button>\r\n\r\n<!-- Popover for Operator Selection -->\r\n<p-popover #opPopover appendTo=\"body\">\r\n <div class=\"w-10rem\">\r\n <p-listbox\r\n [options]=\"[\r\n { label: 'and' | translate, value: 'and' },\r\n { label: 'or' | translate, value: 'or' }\r\n ]\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n [(ngModel)]=\"group.logicalOperator\"\r\n (onChange)=\"updateValue(); opPopover.toggle($event)\"\r\n [style]=\"{ width: '100%' }\">\r\n <ng-template let-item pTemplate=\"item\">\r\n {{ item.label }}\r\n </ng-template>\r\n </p-listbox>\r\n </div>\r\n</p-popover>\r\n\r\n</div>\r\n\r\n\r\n\r\n <!-- Group Header -->\r\n <div class=\"group-header-wrapper mb-4 pl-8\">\r\n <div class=\"group-header flex justify-between items-center\">\r\n\r\n <div class=\"flex gap-1\">\r\n <button\r\n pButton\r\n icon=\"pi pi-plus\"\r\n type=\"button\"\r\n class=\"p-button-success p-button p-button-sm !p-1.5 rounded-md transition-colors\"\r\n (click)=\"addCondition(groupIndex)\"\r\n pTooltip=\"{{ 'ADD_CONDITION' | translate }}\"\r\n tooltipPosition=\"top\">\r\n </button>\r\n <button\r\n *ngIf=\"groups.length > 1\"\r\n pButton\r\n icon=\"pi pi-times\"\r\n type=\"button\"\r\n class=\"p-button-danger p-button p-button-sm !p-1.5 rounded-md transition-colors\"\r\n (click)=\"removeGroup(groupIndex)\"\r\n pTooltip=\"{{ 'DELETE_GROUP' | translate }}\"\r\n tooltipPosition=\"top\">\r\n </button>\r\n</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Conditions in this group -->\r\n <div *ngFor=\"let condition of group.conditions; let conditionIndex = index\"\r\n class=\"p-1 bg-gray-50 \">\r\n <div class=\"grid grid-cols-1 gap-1 items-center\">\r\n <!-- Field Selector -->\r\n <div class=\"md:col-span-2\">\r\n <div class=\"flex w-full rounded-md overflow-hidden border border-gray-300 bg-white shadow-sm\">\r\n <!-- Field Selector Button -->\r\n <div style=\"align-items: center;display: flex; min-width: 130px;max-width: 130px;\" class=\"flex-shrink-0 \">\r\n <button\r\n type=\"button\"\r\n style=\"height: -webkit-fill-available;\"\r\n class=\"flex items-center justify-between w-full px-3 py-2 text-sm bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:z-10\"\r\n (click)=\"overlay.toggle($event)\">\r\n <span style=\"\r\n display: inline-block;\r\n max-width: 100px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n \">{{ getFieldLabel(condition.field) || ('SELECT_FIELD' | translate) }}</span>\r\n <i class=\"pi pi-chevron-down mr-2 ml-2 text-xs\"></i>\r\n </button>\r\n\r\n <!-- Overlay Content -->\r\n <p-popover #overlay appendTo=\"body\">\r\n <div class=\"w-60\">\r\n <p-listbox\r\n tabindex=\"0\"\r\n [options]=\"props['fields']\"\r\n optionLabel=\"label\"\r\n optionValue=\"key\"\r\n [(ngModel)]=\"condition.field\"\r\n (onChange)=\"onFieldChange(condition, groupIndex, conditionIndex); overlay.toggle($event)\"\r\n [style]=\"{ width: '100%' }\">\r\n </p-listbox>\r\n </div>\r\n </p-popover>\r\n </div>\r\n <!-- Operator Button -->\r\n <div style=\"align-items: center;display: flex;\" class=\"flex-shrink-0 border-l border-r border-gray-300\">\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-center w-full px-3 py-2 text-sm bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:z-10\"\r\n style=\"height: -webkit-fill-available;\"\r\n pTooltip=\"{{ getOperatorLabel(condition.operator).label }}\"\r\n (click)=\"opPopover.toggle($event)\">\r\n <i [class]=\"getOperatorLabel(condition.operator).icon\"></i>\r\n </button>\r\n\r\n <p-popover #opPopover appendTo=\"body\">\r\n <div class=\"w-60\">\r\n <p-listbox\r\n [options]=\"condition.operatorsForField\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n [(ngModel)]=\"condition.operator\"\r\n (onChange)=\"opPopover.toggle($event)\"\r\n [style]=\"{ width: '100%' }\">\r\n </p-listbox>\r\n </div>\r\n </p-popover>\r\n </div>\r\n <!-- Form Field -->\r\n <div style=\"background: #f9fafb\" class=\"flex-grow p-1\">\r\n <formly-form\r\n [form]=\"form\"\r\n [fields]=\"[condition.valueFieldConfig]\"\r\n [model]=\"model\">\r\n </formly-form>\r\n </div>\r\n\r\n\r\n\r\n <!-- Delete Button -->\r\n <div style=\"align-items: center;display: flex;\" class=\"flex-shrink-0\">\r\n <button\r\n style=\"height: -webkit-fill-available;\"\r\n class=\"flex items-center justify-center w-full px-3 py-2 text-sm bg-red-50 text-red-600 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-red-500 focus:z-10\"\r\n type=\"button\"\r\n (click)=\"removeCondition(groupIndex, conditionIndex)\"\r\n pTooltip=\"\u062D\u0630\u0641 \u0627\u0644\u0634\u0631\u0637\"\r\n tooltipPosition=\"top\">\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n</div>\r\n </div>\r\n\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Group Footer -->\r\n <div class=\"group-footer flex justify-between items-center mt-3 pt-3 border-t border-gray-200 \">\r\n <small class=\"text-gray-500\">\r\n {{ group.conditions.length }} \u0634\u0631\u0637 \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u0645\u062C\u0645\u0648\u0639\u0629\r\n </small>\r\n <small class=\"text-gray-500\">\r\n <span *ngIf=\"groupIndex > 0\">\r\n \u0645\u0631\u062A\u0628\u0637 \u0645\u0639 \u0627\u0644\u0633\u0627\u0628\u0642\u0629 \u0628\u0640 <strong [class.text-blue-600]=\"group.groupLogicalOperator === 'and'\"\r\n [class.text-amber-600]=\"group.groupLogicalOperator === 'or'\">\r\n {{ group.groupLogicalOperator === 'and' ? '\u0648' : '\u0623\u0648' }}\r\n </strong>\r\n </span>\r\n </small>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Actions -->\r\n<div class=\"flex flex-wrap gap-3 mt-6\">\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-plus\"\r\n [label]=\"'GROUP' | translate\"\r\n class=\"p-button-outlined p-button-sm !bg-white !border !border-blue-500 !text-blue-600 hover:!bg-blue-50 transition-colors flex items-center gap-2\"\r\n (click)=\"addGroup();\">\r\n </button>\r\n\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-plus\"\r\n [label]=\"'CONDITION' | translate\"\r\n class=\"p-button-outlined p-button-sm !bg-white !border !border-blue-500 !text-blue-600 hover:!bg-blue-50 transition-colors flex items-center gap-2\"\r\n (click)=\"addConditionToLastGroup()\">\r\n </button>\r\n\r\n <button\r\n *ngIf=\"groups.length > 0\"\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-trash\"\r\n [label]=\"'CLEAR' | translate\"\r\n class=\"p-button-danger p-button-outlined p-button-sm !bg-white !border !border-red-500 !text-red-600 hover:!bg-red-50 transition-colors flex items-center gap-2\"\r\n (click)=\"clearAll()\">\r\n </button>\r\n</div>\r\n\r\n<!-- Empty State -->\r\n<div *ngIf=\"groups.length === 0\" class=\"empty-state text-center p-6 border-2 border-dashed border-gray-300 rounded-xl bg-gray-50\">\r\n <i class=\"pi pi-search text-4xl text-gray-400 mb-3\"></i>\r\n <p class=\"text-gray-500 mb-4\">{{ 'NO_SEARCH_CONDITIONS' | translate }}</p>\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-plus\"\r\n [label]=\"'ADD_SEARCH_CONDITION' | translate\"\r\n class=\"p-button-outlined !px-4 !py-2\"\r\n (click)=\"addGroup()\">\r\n </button>\r\n</div>\r\n</div>\r\n", styles: [".vertical-connector{position:relative;display:flex;justify-content:center;margin-bottom:1rem}.vertical-line{position:absolute;top:-1rem;height:1rem;width:2px;background-color:#d1d5db}.group-logical-operator-box{position:relative;padding:.25rem .75rem;border-radius:.375rem;font-weight:500;font-size:.75rem;z-index:10;transition:all .3s ease}.group-logical-operator-box.and{background-color:#dbeafe;color:#1d4ed8;border:1px solid #93c5fd}.group-logical-operator-box.or{background-color:#fef3c7;color:#92400e;border:1px solid #fcd34d}.connector-arrow{position:absolute;top:100%;left:50%;transform:translate(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent}.group-logical-operator-box.and .connector-arrow{border-top:6px solid #93c5fd}.group-logical-operator-box.or .connector-arrow{border-top:6px solid #fcd34d}.border-blue-500{border-color:#93c5fd}.border-or-500{border-color:#fcd34d}.border-t-1{border-top-width:1px}.border-b-1{border-bottom-width:1px}.border-r-1{border-right-width:1px}.p-popover-content{padding:5px!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: SelectModule }, { kind: "directive", type: i2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "ngmodule", type: CheckboxModule }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: InputGroupModule }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i6$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ListboxModule }, { kind: "component", type: i7$1.Listbox, selector: "p-listbox, p-listBox, p-list-box", inputs: ["id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "ariaLabel", "selectOnFocus", "searchLocale", "focusOnHover", "filterMessage", "filterFields", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "scrollHeight", "tabindex", "multiple", "styleClass", "listStyle", "listStyleClass", "readonly", "checkbox", "filter", "filterBy", "filterMatchMode", "filterLocale", "metaKeySelection", "dataKey", "showToggleAll", "optionLabel", "optionValue", "optionGroupChildren", "optionGroupLabel", "optionDisabled", "ariaFilterLabel", "filterPlaceHolder", "emptyFilterMessage", "emptyMessage", "group", "options", "filterValue", "selectAll", "striped", "highlightOnSelect", "checkmark", "dragdrop", "fluid"], outputs: ["onChange", "onClick", "onDblClick", "onFilter", "onFocus", "onBlur", "onSelectAllChange", "onLazyLoad", "onDrop"] }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
|
|
1440
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.8", type: QueryBuilderComponent, isStandalone: true, selector: "formly-query-builder", usesInheritance: true, ngImport: i0, template: "<div>\r\n\r\n <!-- Groups Container with Connecting Lines -->\r\n <div class=\"groups-container\" #groupsContainer>\r\n <div *ngFor=\"let group of groups; let groupIndex = index\"\r\n class=\"group-wrapper relative\"\r\n #groupElement>\r\n\r\n <!-- Vertical Connector Line from Previous Group -->\r\n<div *ngIf=\"groupIndex > 0\" class=\"vertical-connector\">\r\n <div class=\"vertical-line\"></div>\r\n\r\n <!-- Operator Button -->\r\n <button\r\n type=\"button\"\r\n class=\"group-logical-operator-box\"\r\n [class.and]=\"group.groupLogicalOperator === 'and'\"\r\n [class.or]=\"group.groupLogicalOperator === 'or'\"\r\n (click)=\"opPopover.toggle($event)\">\r\n <span class=\"operator-text\">\r\n {{ getLogicalOperatorText(group.groupLogicalOperator || 'and') }}\r\n </span>\r\n <div class=\"connector-arrow\"></div>\r\n </button>\r\n\r\n <!-- Popover -->\r\n <p-popover #opPopover appendTo=\"body\">\r\n <div class=\"w-12rem\">\r\n <p-listbox\r\n [options]=\"logicalOperators\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n [(ngModel)]=\"group.groupLogicalOperator\"\r\n (onChange)=\" opPopover.toggle($event)\"\r\n [style]=\"{ width: '100%' }\">\r\n </p-listbox>\r\n </div>\r\n </p-popover>\r\n</div>\r\n\r\n <div class=\"group-container mb-6 p-1 bg-white rounded-lg border-l-4 border-t-1 border-b-1 border-r-1 shadow-sm transition-all duration-300 relative\"\r\n [class.border-blue-500]=\"group.logicalOperator === 'and'\"\r\n [class.border-or-500]=\"group.logicalOperator === 'or'\"\r\n [class.pulse]=\"groupIndex === 0\">\r\n\r\n <!-- Rounded Button for \"\u0631\u0628\u0637 \u0627\u0644\u0634\u0631\u0648\u0637\" on left border -->\r\n<div\r\n class=\"absolute -left-4 top-1/2 transform -translate-y-1/2 z-10 transition-all duration-300\"\r\n\r\n>\r\n\r\n\r\n\r\n<!-- Logical Operator Popover Button -->\r\n<p-button\r\n [severity]=\"group.logicalOperator === 'and' ? 'success' : 'info'\"\r\n [styleClass]=\"group.logicalOperator === 'and'\r\n ? 'p-button-icon-only p-button-rounded p-button-sm logical-and'\r\n : 'p-button-icon-only p-button-rounded p-button-sm logical-or'\"\r\n (click)=\"opPopover.toggle($event)\">\r\n {{group.logicalOperator === 'and' ? ('and' | translate) : ('or' | translate)}}\r\n</p-button>\r\n\r\n<!-- Popover for Operator Selection -->\r\n<p-popover #opPopover appendTo=\"body\">\r\n <div class=\"w-10rem\">\r\n <p-listbox\r\n [options]=\"[\r\n { label: 'and' | translate, value: 'and' },\r\n { label: 'or' | translate, value: 'or' }\r\n ]\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n [(ngModel)]=\"group.logicalOperator\"\r\n (onChange)=\"updateValue(); opPopover.toggle($event)\"\r\n [style]=\"{ width: '100%' }\">\r\n <ng-template let-item pTemplate=\"item\">\r\n {{ item.label }}\r\n </ng-template>\r\n </p-listbox>\r\n </div>\r\n</p-popover>\r\n\r\n</div>\r\n\r\n\r\n\r\n <!-- Group Header -->\r\n <div class=\"group-header-wrapper mb-4 pl-8\">\r\n <div class=\"group-header flex justify-between items-center\">\r\n\r\n <div class=\"flex gap-1\">\r\n <button\r\n pButton\r\n icon=\"pi pi-plus\"\r\n type=\"button\"\r\n class=\"p-button-success p-button p-button-sm !p-1.5 rounded-md transition-colors\"\r\n (click)=\"addCondition(groupIndex)\"\r\n pTooltip=\"{{ 'ADD_CONDITION' | translate }}\"\r\n tooltipPosition=\"top\">\r\n </button>\r\n <button\r\n *ngIf=\"groups.length > 1\"\r\n pButton\r\n icon=\"pi pi-times\"\r\n type=\"button\"\r\n class=\"p-button-danger p-button p-button-sm !p-1.5 rounded-md transition-colors\"\r\n (click)=\"removeGroup(groupIndex)\"\r\n pTooltip=\"{{ 'DELETE_GROUP' | translate }}\"\r\n tooltipPosition=\"top\">\r\n </button>\r\n</div>\r\n </div>\r\n </div>\r\n\r\n <!-- Conditions in this group -->\r\n <div *ngFor=\"let condition of group.conditions; let conditionIndex = index\"\r\n class=\"p-1 bg-gray-50 \">\r\n <div class=\"grid grid-cols-1 gap-1 items-center\">\r\n <!-- Field Selector -->\r\n <div class=\"md:col-span-2\">\r\n <div class=\"flex w-full rounded-md overflow-hidden border border-gray-300 bg-white shadow-sm\">\r\n <!-- Field Selector Button -->\r\n <div style=\"align-items: center;display: flex; min-width: 130px;max-width: 130px;\" class=\"flex-shrink-0 \">\r\n <button\r\n type=\"button\"\r\n style=\"height: -webkit-fill-available;\"\r\n class=\"flex items-center justify-between w-full px-3 py-2 text-sm bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:z-10\"\r\n (click)=\"overlay.toggle($event)\">\r\n <span style=\"\r\n display: inline-block;\r\n max-width: 100px;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n \">{{ getFieldLabel(condition.field) || ('SELECT_FIELD' | translate) }}</span>\r\n <i class=\"pi pi-chevron-down mr-2 ml-2 text-xs\"></i>\r\n </button>\r\n\r\n <!-- Overlay Content -->\r\n <p-popover #overlay appendTo=\"body\">\r\n <div class=\"w-60\">\r\n <p-listbox\r\n tabindex=\"0\"\r\n [options]=\"props['fields']\"\r\n optionLabel=\"label\"\r\n optionValue=\"key\"\r\n [(ngModel)]=\"condition.field\"\r\n (onChange)=\"onFieldChange(condition, groupIndex, conditionIndex); overlay.toggle($event)\"\r\n [style]=\"{ width: '100%' }\">\r\n </p-listbox>\r\n </div>\r\n </p-popover>\r\n </div>\r\n <!-- Operator Button -->\r\n <div style=\"align-items: center;display: flex;\" class=\"flex-shrink-0 border-l border-r border-gray-300\">\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-center w-full px-3 py-2 text-sm bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:z-10\"\r\n style=\"height: -webkit-fill-available;\"\r\n pTooltip=\"{{ getOperatorLabel(condition.operator).label }}\"\r\n (click)=\"opPopover.toggle($event)\">\r\n <i [class]=\"getOperatorLabel(condition.operator).icon\"></i>\r\n </button>\r\n\r\n <p-popover #opPopover appendTo=\"body\">\r\n <div class=\"w-60\">\r\n <p-listbox\r\n [options]=\"condition.operatorsForField\"\r\n optionLabel=\"label\"\r\n optionValue=\"value\"\r\n [(ngModel)]=\"condition.operator\"\r\n (onChange)=\"opPopover.toggle($event)\"\r\n [style]=\"{ width: '100%' }\">\r\n </p-listbox>\r\n </div>\r\n </p-popover>\r\n </div>\r\n <!-- Form Field -->\r\n <div style=\"background: #f9fafb\" class=\"flex-grow p-1\">\r\n <formly-form\r\n [form]=\"form\"\r\n [fields]=\"[condition.valueFieldConfig]\"\r\n [model]=\"model\">\r\n </formly-form>\r\n </div>\r\n\r\n\r\n\r\n <!-- Delete Button -->\r\n <div style=\"align-items: center;display: flex;\" class=\"flex-shrink-0\">\r\n <button\r\n style=\"height: -webkit-fill-available;\"\r\n class=\"flex items-center justify-center w-full px-3 py-2 text-sm bg-red-50 text-red-600 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-red-500 focus:z-10\"\r\n type=\"button\"\r\n (click)=\"removeCondition(groupIndex, conditionIndex)\"\r\n pTooltip=\"\u062D\u0630\u0641 \u0627\u0644\u0634\u0631\u0637\"\r\n tooltipPosition=\"top\">\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n</div>\r\n </div>\r\n\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Group Footer -->\r\n <div class=\"group-footer flex justify-between items-center mt-3 pt-3 border-t border-gray-200 \">\r\n <small class=\"text-gray-500\">\r\n {{ group.conditions.length }} \u0634\u0631\u0637 \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u0645\u062C\u0645\u0648\u0639\u0629\r\n </small>\r\n <small class=\"text-gray-500\">\r\n <span *ngIf=\"groupIndex > 0\">\r\n \u0645\u0631\u062A\u0628\u0637 \u0645\u0639 \u0627\u0644\u0633\u0627\u0628\u0642\u0629 \u0628\u0640 <strong [class.text-blue-600]=\"group.groupLogicalOperator === 'and'\"\r\n [class.text-amber-600]=\"group.groupLogicalOperator === 'or'\">\r\n {{ group.groupLogicalOperator === 'and' ? '\u0648' : '\u0623\u0648' }}\r\n </strong>\r\n </span>\r\n </small>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Actions -->\r\n<div class=\"flex flex-wrap gap-3 mt-6\">\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-plus\"\r\n [label]=\"'GROUP' | translate\"\r\n class=\"p-button-outlined p-button-sm !bg-white !border !border-blue-500 !text-blue-600 hover:!bg-blue-50 transition-colors flex items-center gap-2\"\r\n (click)=\"addGroup();\">\r\n </button>\r\n\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-plus\"\r\n [label]=\"'CONDITION' | translate\"\r\n class=\"p-button-outlined p-button-sm !bg-white !border !border-blue-500 !text-blue-600 hover:!bg-blue-50 transition-colors flex items-center gap-2\"\r\n (click)=\"addConditionToLastGroup()\">\r\n </button>\r\n\r\n <button\r\n *ngIf=\"groups.length > 0\"\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-trash\"\r\n [label]=\"'CLEAR' | translate\"\r\n class=\"p-button-danger p-button-outlined p-button-sm !bg-white !border !border-red-500 !text-red-600 hover:!bg-red-50 transition-colors flex items-center gap-2\"\r\n (click)=\"clearAll()\">\r\n </button>\r\n</div>\r\n\r\n<!-- Empty State -->\r\n<div *ngIf=\"groups.length === 0\" class=\"empty-state text-center p-6 border-2 border-dashed border-gray-300 rounded-xl bg-gray-50\">\r\n <i class=\"pi pi-search text-4xl text-gray-400 mb-3\"></i>\r\n <p class=\"text-gray-500 mb-4\">{{ 'NO_SEARCH_CONDITIONS' | translate }}</p>\r\n <button\r\n pButton\r\n type=\"button\"\r\n icon=\"pi pi-plus\"\r\n [label]=\"'ADD_SEARCH_CONDITION' | translate\"\r\n class=\"p-button-outlined !px-4 !py-2\"\r\n (click)=\"addGroup()\">\r\n </button>\r\n</div>\r\n</div>\r\n", styles: [".vertical-connector{position:relative;display:flex;justify-content:center;margin-bottom:1rem}.vertical-line{position:absolute;top:-1rem;height:1rem;width:2px;background-color:#d1d5db}.group-logical-operator-box{position:relative;padding:.25rem .75rem;border-radius:.375rem;font-weight:500;font-size:.75rem;z-index:10;transition:all .3s ease}.group-logical-operator-box.and{background-color:#dbeafe;color:#1d4ed8;border:1px solid #93c5fd}.group-logical-operator-box.or{background-color:#fef3c7;color:#92400e;border:1px solid #fcd34d}.connector-arrow{position:absolute;top:100%;left:50%;transform:translate(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent}.group-logical-operator-box.and .connector-arrow{border-top:6px solid #93c5fd}.group-logical-operator-box.or .connector-arrow{border-top:6px solid #fcd34d}.border-blue-500{border-color:#93c5fd}.border-or-500{border-color:#fcd34d}.border-t-1{border-top-width:1px}.border-b-1{border-bottom-width:1px}.border-r-1{border-right-width:1px}.p-popover-content{padding:5px!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: SelectModule }, { kind: "directive", type: i2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "ngmodule", type: CheckboxModule }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: InputGroupModule }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i6$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: ListboxModule }, { kind: "component", type: i7$1.Listbox, selector: "p-listbox, p-listBox, p-list-box", inputs: ["id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "ariaLabel", "selectOnFocus", "searchLocale", "focusOnHover", "filterMessage", "filterFields", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "scrollHeight", "tabindex", "multiple", "styleClass", "listStyle", "listStyleClass", "readonly", "checkbox", "filter", "filterBy", "filterMatchMode", "filterLocale", "metaKeySelection", "dataKey", "showToggleAll", "optionLabel", "optionValue", "optionGroupChildren", "optionGroupLabel", "optionDisabled", "ariaFilterLabel", "filterPlaceHolder", "emptyFilterMessage", "emptyMessage", "group", "options", "filterValue", "selectAll", "striped", "highlightOnSelect", "checkmark", "dragdrop", "fluid"], outputs: ["onChange", "onClick", "onDblClick", "onFilter", "onFocus", "onBlur", "onSelectAllChange", "onLazyLoad", "onDrop"] }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
|
|
1256
1441
|
}
|
|
1257
1442
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: QueryBuilderComponent, decorators: [{
|
|
1258
1443
|
type: Component,
|
|
@@ -1322,7 +1507,7 @@ class TabTypeComponent extends FieldType {
|
|
|
1322
1507
|
<i *ngIf="tab.props['leftIcon']" [class]="tab.props['leftIcon']" class="text-sm"></i>
|
|
1323
1508
|
|
|
1324
1509
|
<!-- Label -->
|
|
1325
|
-
<span>{{ (tab.props['
|
|
1510
|
+
<span>{{ (tab.props['tabLabel'] ?? '') | translate }}</span>
|
|
1326
1511
|
|
|
1327
1512
|
<!-- Badge -->
|
|
1328
1513
|
|
|
@@ -1358,7 +1543,7 @@ class TabTypeComponent extends FieldType {
|
|
|
1358
1543
|
</p-tabpanel>
|
|
1359
1544
|
</p-tabpanels>
|
|
1360
1545
|
</p-tabs>
|
|
1361
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2$
|
|
1546
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2$2.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2$2.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2$2.TabPanel, selector: "p-tabpanel", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: i2$2.TabList, selector: "p-tablist" }, { kind: "component", type: i2$2.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i9.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "component", type: FormlyField, selector: "formly-field", inputs: ["field"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i10.TranslatePipe, name: "translate" }] });
|
|
1362
1547
|
}
|
|
1363
1548
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: TabTypeComponent, decorators: [{
|
|
1364
1549
|
type: Component,
|
|
@@ -1383,7 +1568,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImpor
|
|
|
1383
1568
|
<i *ngIf="tab.props['leftIcon']" [class]="tab.props['leftIcon']" class="text-sm"></i>
|
|
1384
1569
|
|
|
1385
1570
|
<!-- Label -->
|
|
1386
|
-
<span>{{ (tab.props['
|
|
1571
|
+
<span>{{ (tab.props['tabLabel'] ?? '') | translate }}</span>
|
|
1387
1572
|
|
|
1388
1573
|
<!-- Badge -->
|
|
1389
1574
|
|
|
@@ -1425,6 +1610,999 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImpor
|
|
|
1425
1610
|
}]
|
|
1426
1611
|
}] });
|
|
1427
1612
|
|
|
1613
|
+
// columns-builder.component.ts
|
|
1614
|
+
class ColumnsBuilderComponent extends FieldType {
|
|
1615
|
+
dragStartIndex = -1;
|
|
1616
|
+
_columnsForm;
|
|
1617
|
+
aggregateFunctions = [
|
|
1618
|
+
{ label: 'Sum', value: 'sum' },
|
|
1619
|
+
{ label: 'Average', value: 'avg' },
|
|
1620
|
+
{ label: 'Count', value: 'count' },
|
|
1621
|
+
{ label: 'Minimum', value: 'min' },
|
|
1622
|
+
{ label: 'Maximum', value: 'max' }
|
|
1623
|
+
];
|
|
1624
|
+
get columns() {
|
|
1625
|
+
return this.props['fields'] || [];
|
|
1626
|
+
}
|
|
1627
|
+
get columnsForm() {
|
|
1628
|
+
return this._columnsForm;
|
|
1629
|
+
}
|
|
1630
|
+
get visibleColumnsCount() {
|
|
1631
|
+
return this.columns.filter(col => this.isColumnVisible(col.key)).length;
|
|
1632
|
+
}
|
|
1633
|
+
get numericColumnsCount() {
|
|
1634
|
+
return this.columns.filter(col => col.isNumeric).length;
|
|
1635
|
+
}
|
|
1636
|
+
get aggregatedColumnsCount() {
|
|
1637
|
+
return this.columns.filter(col => this.hasAggregateFunction(col.key)).length;
|
|
1638
|
+
}
|
|
1639
|
+
ngOnInit() {
|
|
1640
|
+
this.initializeForm();
|
|
1641
|
+
this.setupValueChanges();
|
|
1642
|
+
}
|
|
1643
|
+
// Form Methods
|
|
1644
|
+
initializeForm() {
|
|
1645
|
+
const parentForm = this.form;
|
|
1646
|
+
const fieldKey = this.field.key;
|
|
1647
|
+
if (!fieldKey)
|
|
1648
|
+
return;
|
|
1649
|
+
// Get or create the FormGroup in the parent form
|
|
1650
|
+
let formGroup = parentForm.get(fieldKey);
|
|
1651
|
+
if (!formGroup || !(formGroup instanceof FormGroup)) {
|
|
1652
|
+
// Create new FormGroup
|
|
1653
|
+
formGroup = new FormGroup({});
|
|
1654
|
+
parentForm.setControl(fieldKey, formGroup);
|
|
1655
|
+
// Initialize with existing model data
|
|
1656
|
+
const initialModel = this.model[fieldKey] || {};
|
|
1657
|
+
this.columns.forEach(column => {
|
|
1658
|
+
const columnConfig = initialModel[column.key] || {};
|
|
1659
|
+
const columnGroup = new FormGroup({
|
|
1660
|
+
isVisible: new FormControl(columnConfig.isVisible ?? column.defaultVisible),
|
|
1661
|
+
displayName: new FormControl(columnConfig.displayName ?? column.label),
|
|
1662
|
+
aggregateFunction: new FormControl(columnConfig.aggregateFunction ?? ''),
|
|
1663
|
+
order: new FormControl(columnConfig.order ?? 0)
|
|
1664
|
+
});
|
|
1665
|
+
formGroup.addControl(column.key, columnGroup);
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
// Store the FormGroup reference
|
|
1669
|
+
this._columnsForm = formGroup;
|
|
1670
|
+
// Initialize the model with current form values
|
|
1671
|
+
this.updateModel();
|
|
1672
|
+
}
|
|
1673
|
+
setupValueChanges() {
|
|
1674
|
+
// Listen to form changes and update the model
|
|
1675
|
+
this.columnsForm.valueChanges.subscribe(() => {
|
|
1676
|
+
this.updateModel();
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
updateModel() {
|
|
1680
|
+
const fieldKey = this.field.key;
|
|
1681
|
+
if (fieldKey) {
|
|
1682
|
+
// Update the model with current form values
|
|
1683
|
+
this.model[fieldKey] = this.columnsForm.value;
|
|
1684
|
+
// Trigger form control update to notify Formly
|
|
1685
|
+
this.formControl.setValue(this.columnsForm.value);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
// Called when form controls change
|
|
1689
|
+
onFormChange() {
|
|
1690
|
+
this.updateModel();
|
|
1691
|
+
}
|
|
1692
|
+
// Drag & Drop Methods
|
|
1693
|
+
onDragStart(event, index) {
|
|
1694
|
+
this.dragStartIndex = index;
|
|
1695
|
+
event.dataTransfer?.setData('text/plain', index.toString());
|
|
1696
|
+
}
|
|
1697
|
+
onDragOver(event) {
|
|
1698
|
+
event.preventDefault();
|
|
1699
|
+
}
|
|
1700
|
+
onDrop(event, dropIndex) {
|
|
1701
|
+
event.preventDefault();
|
|
1702
|
+
const dragIndex = this.dragStartIndex;
|
|
1703
|
+
if (dragIndex !== -1 && dragIndex !== dropIndex) {
|
|
1704
|
+
// Reorder the columns array
|
|
1705
|
+
const movedItem = this.columns[dragIndex];
|
|
1706
|
+
const newColumns = [...this.columns];
|
|
1707
|
+
newColumns.splice(dragIndex, 1);
|
|
1708
|
+
newColumns.splice(dropIndex, 0, movedItem);
|
|
1709
|
+
// Update the props to trigger change detection
|
|
1710
|
+
this.props['fields'] = newColumns;
|
|
1711
|
+
// Update order in form controls
|
|
1712
|
+
this.updateColumnOrders();
|
|
1713
|
+
// Update model after reordering
|
|
1714
|
+
this.updateModel();
|
|
1715
|
+
}
|
|
1716
|
+
this.removeDragStyles(event);
|
|
1717
|
+
}
|
|
1718
|
+
onDragEnter(event) {
|
|
1719
|
+
event.currentTarget.classList.add('border-blue-500', 'bg-blue-50');
|
|
1720
|
+
}
|
|
1721
|
+
onDragLeave(event) {
|
|
1722
|
+
this.removeDragStyles(event);
|
|
1723
|
+
}
|
|
1724
|
+
removeDragStyles(event) {
|
|
1725
|
+
event.currentTarget.classList.remove('border-blue-500', 'bg-blue-50');
|
|
1726
|
+
}
|
|
1727
|
+
updateColumnOrders() {
|
|
1728
|
+
// Store the order in form controls
|
|
1729
|
+
this.columns.forEach((column, index) => {
|
|
1730
|
+
const orderControl = this.getColumnControl(column.key, 'order');
|
|
1731
|
+
if (!orderControl) {
|
|
1732
|
+
const columnGroup = this.columnsForm.get(column.key);
|
|
1733
|
+
if (columnGroup && !columnGroup.get('order')) {
|
|
1734
|
+
columnGroup.addControl('order', new FormControl(index));
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
orderControl.setValue(index);
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
getColumnControl(columnKey, controlName) {
|
|
1743
|
+
const columnGroup = this.columnsForm.get(columnKey);
|
|
1744
|
+
if (!columnGroup) {
|
|
1745
|
+
const fallbackControl = new FormControl(this.getDefaultValue(controlName));
|
|
1746
|
+
return fallbackControl;
|
|
1747
|
+
}
|
|
1748
|
+
const control = columnGroup.get(controlName);
|
|
1749
|
+
if (!control) {
|
|
1750
|
+
const fallbackControl = new FormControl(this.getDefaultValue(controlName));
|
|
1751
|
+
columnGroup.addControl(controlName, fallbackControl);
|
|
1752
|
+
return fallbackControl;
|
|
1753
|
+
}
|
|
1754
|
+
return control;
|
|
1755
|
+
}
|
|
1756
|
+
getDefaultValue(controlName) {
|
|
1757
|
+
switch (controlName) {
|
|
1758
|
+
case 'isVisible': return true;
|
|
1759
|
+
case 'displayName': return '';
|
|
1760
|
+
case 'aggregateFunction': return '';
|
|
1761
|
+
case 'order': return 0;
|
|
1762
|
+
default: return null;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
isColumnVisible(columnKey) {
|
|
1766
|
+
const control = this.getColumnControl(columnKey, 'isVisible');
|
|
1767
|
+
return control?.value === true;
|
|
1768
|
+
}
|
|
1769
|
+
hasAggregateFunction(columnKey) {
|
|
1770
|
+
const control = this.getColumnControl(columnKey, 'aggregateFunction');
|
|
1771
|
+
return !!control?.value;
|
|
1772
|
+
}
|
|
1773
|
+
getAggregateFunctionValue(columnKey) {
|
|
1774
|
+
const control = this.getColumnControl(columnKey, 'aggregateFunction');
|
|
1775
|
+
return control?.value || '';
|
|
1776
|
+
}
|
|
1777
|
+
toggleAll(visible) {
|
|
1778
|
+
this.columns.forEach(column => {
|
|
1779
|
+
const visibleControl = this.getColumnControl(column.key, 'isVisible');
|
|
1780
|
+
if (visibleControl) {
|
|
1781
|
+
visibleControl.setValue(visible);
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
this.updateModel();
|
|
1785
|
+
}
|
|
1786
|
+
resetToDefault() {
|
|
1787
|
+
this.columns.forEach((column, index) => {
|
|
1788
|
+
const columnGroup = this.columnsForm.get(column.key);
|
|
1789
|
+
if (columnGroup) {
|
|
1790
|
+
columnGroup.get('isVisible')?.setValue(column.defaultVisible);
|
|
1791
|
+
columnGroup.get('displayName')?.setValue(column.label);
|
|
1792
|
+
columnGroup.get('aggregateFunction')?.setValue('');
|
|
1793
|
+
columnGroup.get('order')?.setValue(index);
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
this.updateModel();
|
|
1797
|
+
}
|
|
1798
|
+
getAggregateLabel(functionName) {
|
|
1799
|
+
const func = this.aggregateFunctions.find(f => f.value === functionName);
|
|
1800
|
+
return func ? func.label : functionName;
|
|
1801
|
+
}
|
|
1802
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: ColumnsBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1803
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.8", type: ColumnsBuilderComponent, isStandalone: true, selector: "formly-columns-builder", usesInheritance: true, ngImport: i0, template: `
|
|
1804
|
+
<div>
|
|
1805
|
+
<!-- Header -->
|
|
1806
|
+
<div class="mb-6">
|
|
1807
|
+
<h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || 'Columns Management' }}</h5>
|
|
1808
|
+
<p class="text-gray-600 text-sm">{{ props.description || 'Select and configure columns to display' }}</p>
|
|
1809
|
+
</div>
|
|
1810
|
+
|
|
1811
|
+
<!-- Quick Actions -->
|
|
1812
|
+
<div class="flex flex-wrap gap-2 mb-6">
|
|
1813
|
+
<button
|
|
1814
|
+
type="button"
|
|
1815
|
+
(click)="toggleAll(true)"
|
|
1816
|
+
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|
1817
|
+
>
|
|
1818
|
+
<i class="pi pi-eye"></i>
|
|
1819
|
+
Show All
|
|
1820
|
+
</button>
|
|
1821
|
+
<button
|
|
1822
|
+
type="button"
|
|
1823
|
+
(click)="toggleAll(false)"
|
|
1824
|
+
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|
1825
|
+
>
|
|
1826
|
+
<i class="pi pi-eye-slash"></i>
|
|
1827
|
+
Hide All
|
|
1828
|
+
</button>
|
|
1829
|
+
<button
|
|
1830
|
+
type="button"
|
|
1831
|
+
(click)="resetToDefault()"
|
|
1832
|
+
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|
1833
|
+
>
|
|
1834
|
+
<i class="pi pi-refresh"></i>
|
|
1835
|
+
Reset to Default
|
|
1836
|
+
</button>
|
|
1837
|
+
</div>
|
|
1838
|
+
|
|
1839
|
+
<!-- Drag Info -->
|
|
1840
|
+
<div class="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
|
|
1841
|
+
<i class="pi pi-info-circle mr-2"></i>
|
|
1842
|
+
Drag the handle to reorder columns
|
|
1843
|
+
</div>
|
|
1844
|
+
|
|
1845
|
+
<!-- Columns List -->
|
|
1846
|
+
<div class="space-y-3">
|
|
1847
|
+
<div *ngIf="columns.length === 0" class="text-center py-8 px-4 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50">
|
|
1848
|
+
<i class="pi pi-table text-4xl text-gray-400 mb-3"></i>
|
|
1849
|
+
<p class="text-gray-500">No columns available</p>
|
|
1850
|
+
</div>
|
|
1851
|
+
|
|
1852
|
+
<div *ngFor="let column of columns; let i = index"
|
|
1853
|
+
class="bg-gray-50 rounded-lg border border-gray-200 p-1 transition-colors hover:bg-gray-100"
|
|
1854
|
+
draggable="true"
|
|
1855
|
+
(dragstart)="onDragStart($event, i)"
|
|
1856
|
+
(dragover)="onDragOver($event)"
|
|
1857
|
+
(drop)="onDrop($event, i)"
|
|
1858
|
+
(dragenter)="onDragEnter($event)"
|
|
1859
|
+
(dragleave)="onDragLeave($event)">
|
|
1860
|
+
|
|
1861
|
+
<div class="grid grid-cols-12 gap-4 items-center">
|
|
1862
|
+
<!-- Drag Handle -->
|
|
1863
|
+
<div class="col-span-1 flex justify-center cursor-move text-gray-400 hover:text-gray-600"
|
|
1864
|
+
draggable="true"
|
|
1865
|
+
(dragstart)="onDragStart($event, i)">
|
|
1866
|
+
<i class="pi pi-bars"></i>
|
|
1867
|
+
</div>
|
|
1868
|
+
|
|
1869
|
+
<!-- Visibility Toggle -->
|
|
1870
|
+
<div class="col-span-1 flex justify-center">
|
|
1871
|
+
<p-checkbox
|
|
1872
|
+
[binary]="true"
|
|
1873
|
+
[formControl]="getColumnControl(column.key, 'isVisible')"
|
|
1874
|
+
[inputId]="'visible_' + column.key"
|
|
1875
|
+
styleClass="[&>div]:border-gray-300 [&>div]:hover:border-gray-400"
|
|
1876
|
+
(onChange)="onFormChange()"
|
|
1877
|
+
>
|
|
1878
|
+
</p-checkbox>
|
|
1879
|
+
</div>
|
|
1880
|
+
|
|
1881
|
+
<!-- Column Info -->
|
|
1882
|
+
<div class="col-span-12 md:col-span-4">
|
|
1883
|
+
<label [for]="'visible_' + column.key" class="block font-medium text-gray-900 cursor-pointer hover:text-blue-600">
|
|
1884
|
+
{{ column.label }}
|
|
1885
|
+
</label>
|
|
1886
|
+
<p class="text-xs text-gray-500 mt-1">{{ column.key }}</p>
|
|
1887
|
+
</div>
|
|
1888
|
+
|
|
1889
|
+
<!-- Display Name -->
|
|
1890
|
+
<div class="col-span-12 md:col-span-3">
|
|
1891
|
+
<input
|
|
1892
|
+
*ngIf="isColumnVisible(column.key)"
|
|
1893
|
+
type="text"
|
|
1894
|
+
[formControl]="getColumnControl(column.key, 'displayName')"
|
|
1895
|
+
[placeholder]="column.label"
|
|
1896
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
1897
|
+
(blur)="onFormChange()"
|
|
1898
|
+
>
|
|
1899
|
+
</div>
|
|
1900
|
+
|
|
1901
|
+
<!-- Aggregate Function -->
|
|
1902
|
+
<div class="col-span-12 md:col-span-3">
|
|
1903
|
+
<div *ngIf="column.aggregatable && isColumnVisible(column.key)">
|
|
1904
|
+
<p-select
|
|
1905
|
+
[options]="aggregateFunctions"
|
|
1906
|
+
optionLabel="label"
|
|
1907
|
+
optionValue="value"
|
|
1908
|
+
[formControl]="getColumnControl(column.key, 'aggregateFunction')"
|
|
1909
|
+
placeholder="Select aggregate"
|
|
1910
|
+
[showClear]="true"
|
|
1911
|
+
styleClass="w-full"
|
|
1912
|
+
(onChange)="onFormChange()"
|
|
1913
|
+
>
|
|
1914
|
+
</p-select>
|
|
1915
|
+
</div>
|
|
1916
|
+
<span *ngIf="!column.aggregatable && isColumnVisible(column.key)" class="text-xs text-gray-500">
|
|
1917
|
+
No aggregates available
|
|
1918
|
+
</span>
|
|
1919
|
+
</div>
|
|
1920
|
+
</div>
|
|
1921
|
+
|
|
1922
|
+
<!-- Aggregate Info -->
|
|
1923
|
+
<div *ngIf="hasAggregateFunction(column.key) && isColumnVisible(column.key)"
|
|
1924
|
+
class="mt-3 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
|
1925
|
+
<div class="flex items-center gap-2">
|
|
1926
|
+
<i class="pi pi-info-circle text-blue-500 text-sm"></i>
|
|
1927
|
+
<span class="text-sm text-blue-800">
|
|
1928
|
+
Will calculate <strong>{{ getAggregateLabel(getAggregateFunctionValue(column.key)) }}</strong>
|
|
1929
|
+
for this column in grouped data
|
|
1930
|
+
</span>
|
|
1931
|
+
</div>
|
|
1932
|
+
</div>
|
|
1933
|
+
</div>
|
|
1934
|
+
</div>
|
|
1935
|
+
|
|
1936
|
+
<!-- Summary -->
|
|
1937
|
+
<div class="flex flex-col sm:flex-row justify-between items-center gap-4 mt-6 pt-4 border-t border-gray-200">
|
|
1938
|
+
<span class="text-sm text-gray-600">
|
|
1939
|
+
{{ visibleColumnsCount }} of {{ columns.length }} columns visible
|
|
1940
|
+
</span>
|
|
1941
|
+
<div class="flex gap-4 text-sm">
|
|
1942
|
+
<span class="inline-flex items-center gap-1 text-green-600">
|
|
1943
|
+
<i class="pi pi-chart-line"></i>
|
|
1944
|
+
{{ numericColumnsCount }} numeric
|
|
1945
|
+
</span>
|
|
1946
|
+
<span class="inline-flex items-center gap-1 text-blue-600">
|
|
1947
|
+
<i class="pi pi-calculator"></i>
|
|
1948
|
+
{{ aggregatedColumnsCount }} aggregated
|
|
1949
|
+
</span>
|
|
1950
|
+
</div>
|
|
1951
|
+
</div>
|
|
1952
|
+
</div>
|
|
1953
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: DatePickerModule }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i4$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: InputGroupModule }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: ListboxModule }] });
|
|
1954
|
+
}
|
|
1955
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: ColumnsBuilderComponent, decorators: [{
|
|
1956
|
+
type: Component,
|
|
1957
|
+
args: [{
|
|
1958
|
+
selector: 'formly-columns-builder',
|
|
1959
|
+
template: `
|
|
1960
|
+
<div>
|
|
1961
|
+
<!-- Header -->
|
|
1962
|
+
<div class="mb-6">
|
|
1963
|
+
<h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || 'Columns Management' }}</h5>
|
|
1964
|
+
<p class="text-gray-600 text-sm">{{ props.description || 'Select and configure columns to display' }}</p>
|
|
1965
|
+
</div>
|
|
1966
|
+
|
|
1967
|
+
<!-- Quick Actions -->
|
|
1968
|
+
<div class="flex flex-wrap gap-2 mb-6">
|
|
1969
|
+
<button
|
|
1970
|
+
type="button"
|
|
1971
|
+
(click)="toggleAll(true)"
|
|
1972
|
+
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|
1973
|
+
>
|
|
1974
|
+
<i class="pi pi-eye"></i>
|
|
1975
|
+
Show All
|
|
1976
|
+
</button>
|
|
1977
|
+
<button
|
|
1978
|
+
type="button"
|
|
1979
|
+
(click)="toggleAll(false)"
|
|
1980
|
+
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|
1981
|
+
>
|
|
1982
|
+
<i class="pi pi-eye-slash"></i>
|
|
1983
|
+
Hide All
|
|
1984
|
+
</button>
|
|
1985
|
+
<button
|
|
1986
|
+
type="button"
|
|
1987
|
+
(click)="resetToDefault()"
|
|
1988
|
+
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
|
1989
|
+
>
|
|
1990
|
+
<i class="pi pi-refresh"></i>
|
|
1991
|
+
Reset to Default
|
|
1992
|
+
</button>
|
|
1993
|
+
</div>
|
|
1994
|
+
|
|
1995
|
+
<!-- Drag Info -->
|
|
1996
|
+
<div class="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
|
|
1997
|
+
<i class="pi pi-info-circle mr-2"></i>
|
|
1998
|
+
Drag the handle to reorder columns
|
|
1999
|
+
</div>
|
|
2000
|
+
|
|
2001
|
+
<!-- Columns List -->
|
|
2002
|
+
<div class="space-y-3">
|
|
2003
|
+
<div *ngIf="columns.length === 0" class="text-center py-8 px-4 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50">
|
|
2004
|
+
<i class="pi pi-table text-4xl text-gray-400 mb-3"></i>
|
|
2005
|
+
<p class="text-gray-500">No columns available</p>
|
|
2006
|
+
</div>
|
|
2007
|
+
|
|
2008
|
+
<div *ngFor="let column of columns; let i = index"
|
|
2009
|
+
class="bg-gray-50 rounded-lg border border-gray-200 p-1 transition-colors hover:bg-gray-100"
|
|
2010
|
+
draggable="true"
|
|
2011
|
+
(dragstart)="onDragStart($event, i)"
|
|
2012
|
+
(dragover)="onDragOver($event)"
|
|
2013
|
+
(drop)="onDrop($event, i)"
|
|
2014
|
+
(dragenter)="onDragEnter($event)"
|
|
2015
|
+
(dragleave)="onDragLeave($event)">
|
|
2016
|
+
|
|
2017
|
+
<div class="grid grid-cols-12 gap-4 items-center">
|
|
2018
|
+
<!-- Drag Handle -->
|
|
2019
|
+
<div class="col-span-1 flex justify-center cursor-move text-gray-400 hover:text-gray-600"
|
|
2020
|
+
draggable="true"
|
|
2021
|
+
(dragstart)="onDragStart($event, i)">
|
|
2022
|
+
<i class="pi pi-bars"></i>
|
|
2023
|
+
</div>
|
|
2024
|
+
|
|
2025
|
+
<!-- Visibility Toggle -->
|
|
2026
|
+
<div class="col-span-1 flex justify-center">
|
|
2027
|
+
<p-checkbox
|
|
2028
|
+
[binary]="true"
|
|
2029
|
+
[formControl]="getColumnControl(column.key, 'isVisible')"
|
|
2030
|
+
[inputId]="'visible_' + column.key"
|
|
2031
|
+
styleClass="[&>div]:border-gray-300 [&>div]:hover:border-gray-400"
|
|
2032
|
+
(onChange)="onFormChange()"
|
|
2033
|
+
>
|
|
2034
|
+
</p-checkbox>
|
|
2035
|
+
</div>
|
|
2036
|
+
|
|
2037
|
+
<!-- Column Info -->
|
|
2038
|
+
<div class="col-span-12 md:col-span-4">
|
|
2039
|
+
<label [for]="'visible_' + column.key" class="block font-medium text-gray-900 cursor-pointer hover:text-blue-600">
|
|
2040
|
+
{{ column.label }}
|
|
2041
|
+
</label>
|
|
2042
|
+
<p class="text-xs text-gray-500 mt-1">{{ column.key }}</p>
|
|
2043
|
+
</div>
|
|
2044
|
+
|
|
2045
|
+
<!-- Display Name -->
|
|
2046
|
+
<div class="col-span-12 md:col-span-3">
|
|
2047
|
+
<input
|
|
2048
|
+
*ngIf="isColumnVisible(column.key)"
|
|
2049
|
+
type="text"
|
|
2050
|
+
[formControl]="getColumnControl(column.key, 'displayName')"
|
|
2051
|
+
[placeholder]="column.label"
|
|
2052
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
|
|
2053
|
+
(blur)="onFormChange()"
|
|
2054
|
+
>
|
|
2055
|
+
</div>
|
|
2056
|
+
|
|
2057
|
+
<!-- Aggregate Function -->
|
|
2058
|
+
<div class="col-span-12 md:col-span-3">
|
|
2059
|
+
<div *ngIf="column.aggregatable && isColumnVisible(column.key)">
|
|
2060
|
+
<p-select
|
|
2061
|
+
[options]="aggregateFunctions"
|
|
2062
|
+
optionLabel="label"
|
|
2063
|
+
optionValue="value"
|
|
2064
|
+
[formControl]="getColumnControl(column.key, 'aggregateFunction')"
|
|
2065
|
+
placeholder="Select aggregate"
|
|
2066
|
+
[showClear]="true"
|
|
2067
|
+
styleClass="w-full"
|
|
2068
|
+
(onChange)="onFormChange()"
|
|
2069
|
+
>
|
|
2070
|
+
</p-select>
|
|
2071
|
+
</div>
|
|
2072
|
+
<span *ngIf="!column.aggregatable && isColumnVisible(column.key)" class="text-xs text-gray-500">
|
|
2073
|
+
No aggregates available
|
|
2074
|
+
</span>
|
|
2075
|
+
</div>
|
|
2076
|
+
</div>
|
|
2077
|
+
|
|
2078
|
+
<!-- Aggregate Info -->
|
|
2079
|
+
<div *ngIf="hasAggregateFunction(column.key) && isColumnVisible(column.key)"
|
|
2080
|
+
class="mt-3 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
|
2081
|
+
<div class="flex items-center gap-2">
|
|
2082
|
+
<i class="pi pi-info-circle text-blue-500 text-sm"></i>
|
|
2083
|
+
<span class="text-sm text-blue-800">
|
|
2084
|
+
Will calculate <strong>{{ getAggregateLabel(getAggregateFunctionValue(column.key)) }}</strong>
|
|
2085
|
+
for this column in grouped data
|
|
2086
|
+
</span>
|
|
2087
|
+
</div>
|
|
2088
|
+
</div>
|
|
2089
|
+
</div>
|
|
2090
|
+
</div>
|
|
2091
|
+
|
|
2092
|
+
<!-- Summary -->
|
|
2093
|
+
<div class="flex flex-col sm:flex-row justify-between items-center gap-4 mt-6 pt-4 border-t border-gray-200">
|
|
2094
|
+
<span class="text-sm text-gray-600">
|
|
2095
|
+
{{ visibleColumnsCount }} of {{ columns.length }} columns visible
|
|
2096
|
+
</span>
|
|
2097
|
+
<div class="flex gap-4 text-sm">
|
|
2098
|
+
<span class="inline-flex items-center gap-1 text-green-600">
|
|
2099
|
+
<i class="pi pi-chart-line"></i>
|
|
2100
|
+
{{ numericColumnsCount }} numeric
|
|
2101
|
+
</span>
|
|
2102
|
+
<span class="inline-flex items-center gap-1 text-blue-600">
|
|
2103
|
+
<i class="pi pi-calculator"></i>
|
|
2104
|
+
{{ aggregatedColumnsCount }} aggregated
|
|
2105
|
+
</span>
|
|
2106
|
+
</div>
|
|
2107
|
+
</div>
|
|
2108
|
+
</div>
|
|
2109
|
+
`,
|
|
2110
|
+
imports: [
|
|
2111
|
+
CommonModule,
|
|
2112
|
+
FormsModule,
|
|
2113
|
+
ReactiveFormsModule,
|
|
2114
|
+
SelectModule,
|
|
2115
|
+
InputTextModule,
|
|
2116
|
+
ButtonModule,
|
|
2117
|
+
DatePickerModule,
|
|
2118
|
+
CheckboxModule,
|
|
2119
|
+
RadioButtonModule,
|
|
2120
|
+
FormlyForm,
|
|
2121
|
+
TooltipModule,
|
|
2122
|
+
MenuModule,
|
|
2123
|
+
TranslateModule,
|
|
2124
|
+
InputGroupModule,
|
|
2125
|
+
InputGroupAddonModule,
|
|
2126
|
+
PopoverModule,
|
|
2127
|
+
ListboxModule
|
|
2128
|
+
]
|
|
2129
|
+
}]
|
|
2130
|
+
}] });
|
|
2131
|
+
|
|
2132
|
+
// group-builder.component.ts
|
|
2133
|
+
class GroupBuilderComponent extends FieldType {
|
|
2134
|
+
_groups;
|
|
2135
|
+
dragStartIndex = -1;
|
|
2136
|
+
get groups() {
|
|
2137
|
+
return this._groups;
|
|
2138
|
+
}
|
|
2139
|
+
get availableFields() {
|
|
2140
|
+
return this.props['fields'] || [];
|
|
2141
|
+
}
|
|
2142
|
+
get maxGroups() {
|
|
2143
|
+
return this.props['maxGroups'] || 3;
|
|
2144
|
+
}
|
|
2145
|
+
ngOnInit() {
|
|
2146
|
+
this.initializeFormArray();
|
|
2147
|
+
this.setupValueChanges();
|
|
2148
|
+
}
|
|
2149
|
+
initializeFormArray() {
|
|
2150
|
+
const parentForm = this.form;
|
|
2151
|
+
const fieldKey = this.field.key;
|
|
2152
|
+
if (!fieldKey)
|
|
2153
|
+
return;
|
|
2154
|
+
// Get or create the FormArray in the parent form
|
|
2155
|
+
let formArray = parentForm.get(fieldKey);
|
|
2156
|
+
if (!formArray || !(formArray instanceof FormArray)) {
|
|
2157
|
+
// Create new FormArray
|
|
2158
|
+
formArray = new FormArray([]);
|
|
2159
|
+
parentForm.setControl(fieldKey, formArray);
|
|
2160
|
+
// Initialize with existing model data
|
|
2161
|
+
const initialValue = this.model[fieldKey];
|
|
2162
|
+
if (Array.isArray(initialValue)) {
|
|
2163
|
+
initialValue.forEach(group => {
|
|
2164
|
+
formArray.push(this.createGroupControl(group));
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
// Store the FormArray reference
|
|
2169
|
+
this._groups = formArray;
|
|
2170
|
+
}
|
|
2171
|
+
setupValueChanges() {
|
|
2172
|
+
// Listen to form array changes and update the model
|
|
2173
|
+
this.groups.valueChanges.subscribe(() => {
|
|
2174
|
+
this.updateModel();
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
updateModel() {
|
|
2178
|
+
const fieldKey = this.field.key;
|
|
2179
|
+
if (fieldKey) {
|
|
2180
|
+
// Update the model with current form values
|
|
2181
|
+
this.model[fieldKey] = this.groups.value;
|
|
2182
|
+
// Trigger form control update to notify Formly
|
|
2183
|
+
this.formControl.setValue(this.groups.value);
|
|
2184
|
+
// Mark form control as touched/dirty if needed
|
|
2185
|
+
this.formControl.markAsTouched();
|
|
2186
|
+
this.formControl.markAsDirty();
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
// Called when form controls change
|
|
2190
|
+
onFormChange() {
|
|
2191
|
+
this.updateModel();
|
|
2192
|
+
}
|
|
2193
|
+
createGroupControl(group) {
|
|
2194
|
+
return new FormGroup({
|
|
2195
|
+
field: new FormControl(group?.field || ''),
|
|
2196
|
+
displayName: new FormControl(group?.displayName || ''),
|
|
2197
|
+
showTotal: new FormControl(group?.showTotal !== false)
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
addGroup() {
|
|
2201
|
+
if (this.canAddMoreGroups()) {
|
|
2202
|
+
this.groups.push(this.createGroupControl());
|
|
2203
|
+
this.updateModel();
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
removeGroup(index) {
|
|
2207
|
+
this.groups.removeAt(index);
|
|
2208
|
+
this.updateModel();
|
|
2209
|
+
}
|
|
2210
|
+
// Drag & Drop Methods
|
|
2211
|
+
onDragStart(event, index) {
|
|
2212
|
+
this.dragStartIndex = index;
|
|
2213
|
+
event.dataTransfer?.setData('text/plain', index.toString());
|
|
2214
|
+
event.currentTarget.classList.add('opacity-50', 'border-blue-300');
|
|
2215
|
+
}
|
|
2216
|
+
onDragOver(event) {
|
|
2217
|
+
event.preventDefault();
|
|
2218
|
+
}
|
|
2219
|
+
onDrop(event, dropIndex) {
|
|
2220
|
+
event.preventDefault();
|
|
2221
|
+
const dragIndex = this.dragStartIndex;
|
|
2222
|
+
if (dragIndex !== -1 && dragIndex !== dropIndex) {
|
|
2223
|
+
this.moveGroup(dragIndex, dropIndex);
|
|
2224
|
+
}
|
|
2225
|
+
this.removeDragStyles(event);
|
|
2226
|
+
}
|
|
2227
|
+
onDragEnter(event) {
|
|
2228
|
+
event.currentTarget.classList.add('border-blue-500', 'bg-blue-50', 'border-2');
|
|
2229
|
+
}
|
|
2230
|
+
onDragLeave(event) {
|
|
2231
|
+
this.removeDragStyles(event);
|
|
2232
|
+
}
|
|
2233
|
+
onDragEnd(event) {
|
|
2234
|
+
this.removeDragStyles(event);
|
|
2235
|
+
this.dragStartIndex = -1;
|
|
2236
|
+
}
|
|
2237
|
+
removeDragStyles(event) {
|
|
2238
|
+
const element = event.currentTarget;
|
|
2239
|
+
element.classList.remove('border-blue-500', 'bg-blue-50', 'opacity-50', 'border-blue-300', 'border-2');
|
|
2240
|
+
}
|
|
2241
|
+
moveGroup(fromIndex, toIndex) {
|
|
2242
|
+
const movedGroup = this.groups.at(fromIndex);
|
|
2243
|
+
this.groups.removeAt(fromIndex);
|
|
2244
|
+
this.groups.insert(toIndex, movedGroup);
|
|
2245
|
+
this.updateModel();
|
|
2246
|
+
}
|
|
2247
|
+
getGroupControl(index, controlName) {
|
|
2248
|
+
const group = this.groups.at(index);
|
|
2249
|
+
return group.get(controlName);
|
|
2250
|
+
}
|
|
2251
|
+
onFieldChange(index, fieldKey) {
|
|
2252
|
+
const displayNameControl = this.getGroupControl(index, 'displayName');
|
|
2253
|
+
if (!displayNameControl.value && fieldKey) {
|
|
2254
|
+
const field = this.availableFields.find(f => f.key === fieldKey);
|
|
2255
|
+
if (field) {
|
|
2256
|
+
displayNameControl.setValue(field.label);
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
this.updateModel();
|
|
2260
|
+
}
|
|
2261
|
+
getFieldLabel(fieldKey) {
|
|
2262
|
+
const field = this.availableFields.find(f => f.key === fieldKey);
|
|
2263
|
+
return field ? field.label : fieldKey;
|
|
2264
|
+
}
|
|
2265
|
+
canAddMoreGroups() {
|
|
2266
|
+
return this.groups.length < this.maxGroups;
|
|
2267
|
+
}
|
|
2268
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GroupBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
2269
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.8", type: GroupBuilderComponent, isStandalone: true, selector: "formly-group-builder", usesInheritance: true, ngImport: i0, template: `
|
|
2270
|
+
<div>
|
|
2271
|
+
<!-- Header -->
|
|
2272
|
+
<div class="mb-6">
|
|
2273
|
+
<h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || 'Data Grouping' }}</h5>
|
|
2274
|
+
<p class="text-gray-600 text-sm">{{ props.description || 'Group data by fields to create hierarchical views' }}</p>
|
|
2275
|
+
</div>
|
|
2276
|
+
|
|
2277
|
+
<!-- Group Configuration -->
|
|
2278
|
+
<div class="space-y-4">
|
|
2279
|
+
<div class="flex items-center justify-between">
|
|
2280
|
+
<span class="font-medium text-gray-700">Group Levels</span>
|
|
2281
|
+
<button
|
|
2282
|
+
type="button"
|
|
2283
|
+
*ngIf="canAddMoreGroups()"
|
|
2284
|
+
(click)="addGroup()"
|
|
2285
|
+
[disabled]="!canAddMoreGroups()"
|
|
2286
|
+
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-lg hover:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
2287
|
+
>
|
|
2288
|
+
<i class="pi pi-plus"></i>
|
|
2289
|
+
Add Group Level
|
|
2290
|
+
</button>
|
|
2291
|
+
</div>
|
|
2292
|
+
|
|
2293
|
+
<!-- Drag Info -->
|
|
2294
|
+
<div *ngIf="groups.length > 1" class="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
|
|
2295
|
+
<i class="pi pi-info-circle mr-2"></i>
|
|
2296
|
+
Drag the handle to reorder group levels
|
|
2297
|
+
</div>
|
|
2298
|
+
|
|
2299
|
+
<!-- Empty State -->
|
|
2300
|
+
<div *ngIf="groups.length === 0" class="text-center py-8 px-4 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50">
|
|
2301
|
+
<i class="pi pi-layer-group text-4xl text-gray-400 mb-3"></i>
|
|
2302
|
+
<p class="text-gray-500 mb-4">No grouping levels defined</p>
|
|
2303
|
+
<button
|
|
2304
|
+
type="button"
|
|
2305
|
+
(click)="addGroup()"
|
|
2306
|
+
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors"
|
|
2307
|
+
>
|
|
2308
|
+
<i class="pi pi-plus"></i>
|
|
2309
|
+
Add First Group Level
|
|
2310
|
+
</button>
|
|
2311
|
+
</div>
|
|
2312
|
+
|
|
2313
|
+
<!-- Groups List with Drag & Drop -->
|
|
2314
|
+
<div class="space-y-3">
|
|
2315
|
+
<div *ngFor="let group of groups.controls; let i = index"
|
|
2316
|
+
class="bg-gray-50 rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-100"
|
|
2317
|
+
draggable="true"
|
|
2318
|
+
(dragstart)="onDragStart($event, i)"
|
|
2319
|
+
(dragover)="onDragOver($event)"
|
|
2320
|
+
(drop)="onDrop($event, i)"
|
|
2321
|
+
(dragenter)="onDragEnter($event)"
|
|
2322
|
+
(dragleave)="onDragLeave($event)"
|
|
2323
|
+
(dragend)="onDragEnd($event)">
|
|
2324
|
+
|
|
2325
|
+
<div class="grid grid-cols-12 gap-4 items-start">
|
|
2326
|
+
<!-- Drag Handle -->
|
|
2327
|
+
<div *ngIf="groups.length > 1"
|
|
2328
|
+
class="col-span-1 flex justify-center cursor-move text-gray-400 hover:text-gray-600 pt-2"
|
|
2329
|
+
draggable="true"
|
|
2330
|
+
(dragstart)="onDragStart($event, i)">
|
|
2331
|
+
<i class="pi pi-bars"></i>
|
|
2332
|
+
</div>
|
|
2333
|
+
|
|
2334
|
+
<!-- Group Content -->
|
|
2335
|
+
<div class="col-span-11">
|
|
2336
|
+
<div class="flex items-center justify-between mb-3">
|
|
2337
|
+
<div class="flex items-center gap-2">
|
|
2338
|
+
<i class="pi pi-sort-alt text-gray-500"></i>
|
|
2339
|
+
<span class="font-medium text-gray-700">Group Level {{ i + 1 }}</span>
|
|
2340
|
+
</div>
|
|
2341
|
+
<div class="flex items-center gap-1">
|
|
2342
|
+
<button
|
|
2343
|
+
type="button"
|
|
2344
|
+
(click)="removeGroup(i)"
|
|
2345
|
+
class="p-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded transition-colors"
|
|
2346
|
+
[pTooltip]="'Remove'"
|
|
2347
|
+
>
|
|
2348
|
+
<i class="pi pi-times"></i>
|
|
2349
|
+
</button>
|
|
2350
|
+
</div>
|
|
2351
|
+
</div>
|
|
2352
|
+
|
|
2353
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2354
|
+
<!-- Field Selection -->
|
|
2355
|
+
<div>
|
|
2356
|
+
<label class="block text-sm font-medium text-gray-700 mb-2">Field</label>
|
|
2357
|
+
<p-select
|
|
2358
|
+
[options]="availableFields"
|
|
2359
|
+
optionLabel="label"
|
|
2360
|
+
optionValue="key"
|
|
2361
|
+
[formControl]="getGroupControl(i, 'field')"
|
|
2362
|
+
placeholder="Select field"
|
|
2363
|
+
[showClear]="true"
|
|
2364
|
+
(onChange)="onFieldChange(i, $event.value)"
|
|
2365
|
+
styleClass="w-full"
|
|
2366
|
+
>
|
|
2367
|
+
<ng-template pTemplate="selectedItem">
|
|
2368
|
+
<span *ngIf="getGroupControl(i, 'field').value" class="text-gray-900">
|
|
2369
|
+
{{ getFieldLabel(getGroupControl(i, 'field').value) }}
|
|
2370
|
+
</span>
|
|
2371
|
+
<span *ngIf="!getGroupControl(i, 'field').value" class="text-gray-500">Select field</span>
|
|
2372
|
+
</ng-template>
|
|
2373
|
+
</p-select>
|
|
2374
|
+
</div>
|
|
2375
|
+
|
|
2376
|
+
<!-- Display Name -->
|
|
2377
|
+
<div>
|
|
2378
|
+
<label class="block text-sm font-medium text-gray-700 mb-2">Display Name</label>
|
|
2379
|
+
<input
|
|
2380
|
+
type="text"
|
|
2381
|
+
pInputText
|
|
2382
|
+
[formControl]="getGroupControl(i, 'displayName')"
|
|
2383
|
+
placeholder="Display name (optional)"
|
|
2384
|
+
class="w-full text-gray-900"
|
|
2385
|
+
(blur)="onFormChange()"
|
|
2386
|
+
>
|
|
2387
|
+
</div>
|
|
2388
|
+
</div>
|
|
2389
|
+
|
|
2390
|
+
<!-- Show Total Toggle -->
|
|
2391
|
+
<div class="flex items-center gap-2 mt-3">
|
|
2392
|
+
<p-checkbox
|
|
2393
|
+
[binary]="true"
|
|
2394
|
+
[formControl]="getGroupControl(i, 'showTotal')"
|
|
2395
|
+
[inputId]="'showTotal' + i"
|
|
2396
|
+
styleClass="[&>div]:border-gray-300 [&>div]:hover:border-gray-400"
|
|
2397
|
+
(onChange)="onFormChange()"
|
|
2398
|
+
>
|
|
2399
|
+
</p-checkbox>
|
|
2400
|
+
<label [for]="'showTotal' + i" class="text-sm text-gray-700 cursor-pointer hover:text-gray-900">
|
|
2401
|
+
Show total for this group level
|
|
2402
|
+
</label>
|
|
2403
|
+
</div>
|
|
2404
|
+
</div>
|
|
2405
|
+
</div>
|
|
2406
|
+
</div>
|
|
2407
|
+
</div>
|
|
2408
|
+
</div>
|
|
2409
|
+
|
|
2410
|
+
<!-- Grouping Tips -->
|
|
2411
|
+
<div *ngIf="groups.length > 0" class="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
|
2412
|
+
<div class="flex items-center gap-3">
|
|
2413
|
+
<i class="pi pi-info-circle text-blue-500"></i>
|
|
2414
|
+
<div>
|
|
2415
|
+
<p class="text-sm text-blue-800">
|
|
2416
|
+
Data will be grouped hierarchically in the specified order.
|
|
2417
|
+
{{ groups.length }} level{{ groups.length > 1 ? 's' : '' }} defined.
|
|
2418
|
+
</p>
|
|
2419
|
+
</div>
|
|
2420
|
+
</div>
|
|
2421
|
+
</div>
|
|
2422
|
+
</div>
|
|
2423
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: i2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i5.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: DatePickerModule }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i4$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: InputGroupModule }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: ListboxModule }] });
|
|
2424
|
+
}
|
|
2425
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GroupBuilderComponent, decorators: [{
|
|
2426
|
+
type: Component,
|
|
2427
|
+
args: [{
|
|
2428
|
+
selector: 'formly-group-builder',
|
|
2429
|
+
template: `
|
|
2430
|
+
<div>
|
|
2431
|
+
<!-- Header -->
|
|
2432
|
+
<div class="mb-6">
|
|
2433
|
+
<h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || 'Data Grouping' }}</h5>
|
|
2434
|
+
<p class="text-gray-600 text-sm">{{ props.description || 'Group data by fields to create hierarchical views' }}</p>
|
|
2435
|
+
</div>
|
|
2436
|
+
|
|
2437
|
+
<!-- Group Configuration -->
|
|
2438
|
+
<div class="space-y-4">
|
|
2439
|
+
<div class="flex items-center justify-between">
|
|
2440
|
+
<span class="font-medium text-gray-700">Group Levels</span>
|
|
2441
|
+
<button
|
|
2442
|
+
type="button"
|
|
2443
|
+
*ngIf="canAddMoreGroups()"
|
|
2444
|
+
(click)="addGroup()"
|
|
2445
|
+
[disabled]="!canAddMoreGroups()"
|
|
2446
|
+
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-lg hover:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
2447
|
+
>
|
|
2448
|
+
<i class="pi pi-plus"></i>
|
|
2449
|
+
Add Group Level
|
|
2450
|
+
</button>
|
|
2451
|
+
</div>
|
|
2452
|
+
|
|
2453
|
+
<!-- Drag Info -->
|
|
2454
|
+
<div *ngIf="groups.length > 1" class="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
|
|
2455
|
+
<i class="pi pi-info-circle mr-2"></i>
|
|
2456
|
+
Drag the handle to reorder group levels
|
|
2457
|
+
</div>
|
|
2458
|
+
|
|
2459
|
+
<!-- Empty State -->
|
|
2460
|
+
<div *ngIf="groups.length === 0" class="text-center py-8 px-4 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50">
|
|
2461
|
+
<i class="pi pi-layer-group text-4xl text-gray-400 mb-3"></i>
|
|
2462
|
+
<p class="text-gray-500 mb-4">No grouping levels defined</p>
|
|
2463
|
+
<button
|
|
2464
|
+
type="button"
|
|
2465
|
+
(click)="addGroup()"
|
|
2466
|
+
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors"
|
|
2467
|
+
>
|
|
2468
|
+
<i class="pi pi-plus"></i>
|
|
2469
|
+
Add First Group Level
|
|
2470
|
+
</button>
|
|
2471
|
+
</div>
|
|
2472
|
+
|
|
2473
|
+
<!-- Groups List with Drag & Drop -->
|
|
2474
|
+
<div class="space-y-3">
|
|
2475
|
+
<div *ngFor="let group of groups.controls; let i = index"
|
|
2476
|
+
class="bg-gray-50 rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-100"
|
|
2477
|
+
draggable="true"
|
|
2478
|
+
(dragstart)="onDragStart($event, i)"
|
|
2479
|
+
(dragover)="onDragOver($event)"
|
|
2480
|
+
(drop)="onDrop($event, i)"
|
|
2481
|
+
(dragenter)="onDragEnter($event)"
|
|
2482
|
+
(dragleave)="onDragLeave($event)"
|
|
2483
|
+
(dragend)="onDragEnd($event)">
|
|
2484
|
+
|
|
2485
|
+
<div class="grid grid-cols-12 gap-4 items-start">
|
|
2486
|
+
<!-- Drag Handle -->
|
|
2487
|
+
<div *ngIf="groups.length > 1"
|
|
2488
|
+
class="col-span-1 flex justify-center cursor-move text-gray-400 hover:text-gray-600 pt-2"
|
|
2489
|
+
draggable="true"
|
|
2490
|
+
(dragstart)="onDragStart($event, i)">
|
|
2491
|
+
<i class="pi pi-bars"></i>
|
|
2492
|
+
</div>
|
|
2493
|
+
|
|
2494
|
+
<!-- Group Content -->
|
|
2495
|
+
<div class="col-span-11">
|
|
2496
|
+
<div class="flex items-center justify-between mb-3">
|
|
2497
|
+
<div class="flex items-center gap-2">
|
|
2498
|
+
<i class="pi pi-sort-alt text-gray-500"></i>
|
|
2499
|
+
<span class="font-medium text-gray-700">Group Level {{ i + 1 }}</span>
|
|
2500
|
+
</div>
|
|
2501
|
+
<div class="flex items-center gap-1">
|
|
2502
|
+
<button
|
|
2503
|
+
type="button"
|
|
2504
|
+
(click)="removeGroup(i)"
|
|
2505
|
+
class="p-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded transition-colors"
|
|
2506
|
+
[pTooltip]="'Remove'"
|
|
2507
|
+
>
|
|
2508
|
+
<i class="pi pi-times"></i>
|
|
2509
|
+
</button>
|
|
2510
|
+
</div>
|
|
2511
|
+
</div>
|
|
2512
|
+
|
|
2513
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2514
|
+
<!-- Field Selection -->
|
|
2515
|
+
<div>
|
|
2516
|
+
<label class="block text-sm font-medium text-gray-700 mb-2">Field</label>
|
|
2517
|
+
<p-select
|
|
2518
|
+
[options]="availableFields"
|
|
2519
|
+
optionLabel="label"
|
|
2520
|
+
optionValue="key"
|
|
2521
|
+
[formControl]="getGroupControl(i, 'field')"
|
|
2522
|
+
placeholder="Select field"
|
|
2523
|
+
[showClear]="true"
|
|
2524
|
+
(onChange)="onFieldChange(i, $event.value)"
|
|
2525
|
+
styleClass="w-full"
|
|
2526
|
+
>
|
|
2527
|
+
<ng-template pTemplate="selectedItem">
|
|
2528
|
+
<span *ngIf="getGroupControl(i, 'field').value" class="text-gray-900">
|
|
2529
|
+
{{ getFieldLabel(getGroupControl(i, 'field').value) }}
|
|
2530
|
+
</span>
|
|
2531
|
+
<span *ngIf="!getGroupControl(i, 'field').value" class="text-gray-500">Select field</span>
|
|
2532
|
+
</ng-template>
|
|
2533
|
+
</p-select>
|
|
2534
|
+
</div>
|
|
2535
|
+
|
|
2536
|
+
<!-- Display Name -->
|
|
2537
|
+
<div>
|
|
2538
|
+
<label class="block text-sm font-medium text-gray-700 mb-2">Display Name</label>
|
|
2539
|
+
<input
|
|
2540
|
+
type="text"
|
|
2541
|
+
pInputText
|
|
2542
|
+
[formControl]="getGroupControl(i, 'displayName')"
|
|
2543
|
+
placeholder="Display name (optional)"
|
|
2544
|
+
class="w-full text-gray-900"
|
|
2545
|
+
(blur)="onFormChange()"
|
|
2546
|
+
>
|
|
2547
|
+
</div>
|
|
2548
|
+
</div>
|
|
2549
|
+
|
|
2550
|
+
<!-- Show Total Toggle -->
|
|
2551
|
+
<div class="flex items-center gap-2 mt-3">
|
|
2552
|
+
<p-checkbox
|
|
2553
|
+
[binary]="true"
|
|
2554
|
+
[formControl]="getGroupControl(i, 'showTotal')"
|
|
2555
|
+
[inputId]="'showTotal' + i"
|
|
2556
|
+
styleClass="[&>div]:border-gray-300 [&>div]:hover:border-gray-400"
|
|
2557
|
+
(onChange)="onFormChange()"
|
|
2558
|
+
>
|
|
2559
|
+
</p-checkbox>
|
|
2560
|
+
<label [for]="'showTotal' + i" class="text-sm text-gray-700 cursor-pointer hover:text-gray-900">
|
|
2561
|
+
Show total for this group level
|
|
2562
|
+
</label>
|
|
2563
|
+
</div>
|
|
2564
|
+
</div>
|
|
2565
|
+
</div>
|
|
2566
|
+
</div>
|
|
2567
|
+
</div>
|
|
2568
|
+
</div>
|
|
2569
|
+
|
|
2570
|
+
<!-- Grouping Tips -->
|
|
2571
|
+
<div *ngIf="groups.length > 0" class="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
|
2572
|
+
<div class="flex items-center gap-3">
|
|
2573
|
+
<i class="pi pi-info-circle text-blue-500"></i>
|
|
2574
|
+
<div>
|
|
2575
|
+
<p class="text-sm text-blue-800">
|
|
2576
|
+
Data will be grouped hierarchically in the specified order.
|
|
2577
|
+
{{ groups.length }} level{{ groups.length > 1 ? 's' : '' }} defined.
|
|
2578
|
+
</p>
|
|
2579
|
+
</div>
|
|
2580
|
+
</div>
|
|
2581
|
+
</div>
|
|
2582
|
+
</div>
|
|
2583
|
+
`,
|
|
2584
|
+
imports: [
|
|
2585
|
+
CommonModule,
|
|
2586
|
+
FormsModule,
|
|
2587
|
+
ReactiveFormsModule,
|
|
2588
|
+
SelectModule,
|
|
2589
|
+
InputTextModule,
|
|
2590
|
+
ButtonModule,
|
|
2591
|
+
DatePickerModule,
|
|
2592
|
+
CheckboxModule,
|
|
2593
|
+
RadioButtonModule,
|
|
2594
|
+
FormlyForm,
|
|
2595
|
+
TooltipModule,
|
|
2596
|
+
MenuModule,
|
|
2597
|
+
TranslateModule,
|
|
2598
|
+
InputGroupModule,
|
|
2599
|
+
InputGroupAddonModule,
|
|
2600
|
+
PopoverModule,
|
|
2601
|
+
ListboxModule
|
|
2602
|
+
]
|
|
2603
|
+
}]
|
|
2604
|
+
}] });
|
|
2605
|
+
|
|
1428
2606
|
// group-type.component.ts
|
|
1429
2607
|
class GroupTypeComponent extends FieldType {
|
|
1430
2608
|
collapsed = this.props.collapsedByDefault ?? false;
|
|
@@ -1557,6 +2735,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImpor
|
|
|
1557
2735
|
|
|
1558
2736
|
const formlyConfig = provideFormlyConfig({
|
|
1559
2737
|
types: [
|
|
2738
|
+
{
|
|
2739
|
+
name: 'tab-type',
|
|
2740
|
+
component: TabTypeComponent
|
|
2741
|
+
},
|
|
1560
2742
|
{
|
|
1561
2743
|
name: 'query-builder',
|
|
1562
2744
|
component: QueryBuilderComponent
|
|
@@ -1566,12 +2748,14 @@ const formlyConfig = provideFormlyConfig({
|
|
|
1566
2748
|
component: SortBuilderComponent
|
|
1567
2749
|
},
|
|
1568
2750
|
{
|
|
1569
|
-
name: 'group',
|
|
2751
|
+
name: 'group-builder',
|
|
2752
|
+
component: GroupBuilderComponent
|
|
1570
2753
|
},
|
|
1571
2754
|
{
|
|
1572
|
-
name: '
|
|
1573
|
-
component:
|
|
2755
|
+
name: 'columns-builder',
|
|
2756
|
+
component: ColumnsBuilderComponent
|
|
1574
2757
|
},
|
|
2758
|
+
{ name: 'group', component: GroupTypeComponent },
|
|
1575
2759
|
],
|
|
1576
2760
|
});
|
|
1577
2761
|
|
|
@@ -1579,5 +2763,5 @@ const formlyConfig = provideFormlyConfig({
|
|
|
1579
2763
|
* Generated bundle index. Do not edit.
|
|
1580
2764
|
*/
|
|
1581
2765
|
|
|
1582
|
-
export { GenericSearchAdvanced, GenericSearchAdvancedModule, GroupTypeComponent, QueryBuilderComponent, QueryBuilderService, SortBuilderComponent, TabTypeComponent, formlyConfig };
|
|
2766
|
+
export { ColumnsBuilderComponent, GenericSearchAdvanced, GenericSearchAdvancedModule, GroupBuilderComponent, GroupTypeComponent, QueryBuilderComponent, QueryBuilderService, SortBuilderComponent, TabTypeComponent, formlyConfig };
|
|
1583
2767
|
//# sourceMappingURL=elite.framework-ng.ui.core-generic-search-advanced.mjs.map
|