@elite.framework/ng.ui.core 1.0.76 → 1.0.77
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/fesm2022/elite.framework-ng.ui.core-base-crud.mjs +1 -21
- package/fesm2022/elite.framework-ng.ui.core-base-crud.mjs.map +1 -1
- package/fesm2022/elite.framework-ng.ui.core-formly-ui-all.mjs +25 -2
- package/fesm2022/elite.framework-ng.ui.core-formly-ui-all.mjs.map +1 -1
- package/fesm2022/elite.framework-ng.ui.core-generic-crud-table.mjs +69 -8
- package/fesm2022/elite.framework-ng.ui.core-generic-crud-table.mjs.map +1 -1
- package/fesm2022/elite.framework-ng.ui.core-generic-report.mjs +479 -0
- package/fesm2022/elite.framework-ng.ui.core-generic-report.mjs.map +1 -0
- package/fesm2022/elite.framework-ng.ui.core-generic-search-advanced.mjs +1333 -558
- package/fesm2022/elite.framework-ng.ui.core-generic-search-advanced.mjs.map +1 -1
- package/formly-ui-all/index.d.ts +2 -1
- package/generic-report/README.md +3 -0
- package/generic-report/index.d.ts +95 -0
- package/generic-search-advanced/index.d.ts +133 -50
- package/package.json +32 -28
|
@@ -1,29 +1,45 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { NgModule,
|
|
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 {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import * as i7 from '@ngx-translate/core';
|
|
5
|
+
import * as i4$1 from '@angular/forms';
|
|
6
|
+
import { FormGroup, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
7
|
+
import { FormlyForm, FieldType, FormlyField, provideFormlyConfig } from '@ngx-formly/core';
|
|
8
|
+
import * as i10 from '@ngx-translate/core';
|
|
10
9
|
import { TranslateService, TranslateModule } from '@ngx-translate/core';
|
|
11
10
|
import { ToolbarModule } from 'primeng/toolbar';
|
|
12
|
-
import * as
|
|
11
|
+
import * as i4 from 'primeng/button';
|
|
13
12
|
import { ButtonModule } from 'primeng/button';
|
|
14
|
-
import { PopoverModule } from 'primeng/popover';
|
|
15
13
|
import { InputIconModule } from 'primeng/inputicon';
|
|
16
|
-
import * as i3$1 from 'primeng/iconfield';
|
|
17
14
|
import { IconFieldModule } from 'primeng/iconfield';
|
|
18
|
-
import * as
|
|
15
|
+
import * as i5 from 'primeng/inputtext';
|
|
19
16
|
import { InputTextModule } from 'primeng/inputtext';
|
|
20
|
-
import * as
|
|
17
|
+
import * as i6 from 'primeng/drawer';
|
|
21
18
|
import { DrawerModule } from 'primeng/drawer';
|
|
22
|
-
import
|
|
23
|
-
import
|
|
19
|
+
import { QueryParser } from '@elite.framework/ng.core/services';
|
|
20
|
+
import * as i8 from 'primeng/inputgroupaddon';
|
|
21
|
+
import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
|
|
22
|
+
import * as i7 from 'primeng/inputgroup';
|
|
23
|
+
import { InputGroupModule } from 'primeng/inputgroup';
|
|
24
|
+
import * as i9 from 'primeng/badge';
|
|
25
|
+
import { BadgeModule } from 'primeng/badge';
|
|
26
|
+
import * as i2 from 'primeng/api';
|
|
24
27
|
import * as i3 from 'primeng/select';
|
|
25
28
|
import { SelectModule } from 'primeng/select';
|
|
29
|
+
import * as i5$1 from 'primeng/dragdrop';
|
|
30
|
+
import { DragDropModule } from 'primeng/dragdrop';
|
|
31
|
+
import * as i6$1 from 'primeng/tooltip';
|
|
32
|
+
import { TooltipModule } from 'primeng/tooltip';
|
|
33
|
+
import * as i7$1 from 'primeng/listbox';
|
|
34
|
+
import { ListboxModule } from 'primeng/listbox';
|
|
35
|
+
import { CheckboxModule } from 'primeng/checkbox';
|
|
26
36
|
import { DatePickerModule } from 'primeng/datepicker';
|
|
37
|
+
import { RadioButtonModule } from 'primeng/radiobutton';
|
|
38
|
+
import { MenuModule } from 'primeng/menu';
|
|
39
|
+
import * as i6$2 from 'primeng/popover';
|
|
40
|
+
import { PopoverModule } from 'primeng/popover';
|
|
41
|
+
import * as i2$1 from 'primeng/tabs';
|
|
42
|
+
import { TabsModule } from 'primeng/tabs';
|
|
27
43
|
|
|
28
44
|
class GenericSearchAdvancedModule {
|
|
29
45
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GenericSearchAdvancedModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
@@ -39,81 +55,164 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImpor
|
|
|
39
55
|
|
|
40
56
|
// query-builder.service.ts
|
|
41
57
|
class QueryBuilderService {
|
|
58
|
+
translate = inject(TranslateService);
|
|
59
|
+
// Field type configuration that matches QueryBuilderComponent
|
|
60
|
+
fieldTypeConfig = {
|
|
61
|
+
'input': { defaultOperator: 'contains', defaultValue: '', operators: ['eq', 'ne', 'contains', 'startswith', 'endswith'] },
|
|
62
|
+
'number': { defaultOperator: 'eq', defaultValue: null, operators: ['eq', 'ne', 'gt', 'ge', 'lt', 'le'] },
|
|
63
|
+
'datepicker': { defaultOperator: 'eq', defaultValue: null, operators: ['eq', 'ne', 'gt', 'ge', 'lt', 'le'] },
|
|
64
|
+
'select': { defaultOperator: 'eq', defaultValue: '', operators: ['eq', 'ne', 'in', 'notin'] },
|
|
65
|
+
'checkbox': { defaultOperator: 'eq', defaultValue: null, operators: ['eq', 'ne'] },
|
|
66
|
+
'generic-selector': { defaultOperator: 'eq', defaultValue: null, operators: ['eq', 'ne', 'in', 'notin'] },
|
|
67
|
+
'switch': { defaultOperator: 'eq', defaultValue: null, operators: ['eq', 'ne'] },
|
|
68
|
+
'radio': { defaultOperator: 'eq', defaultValue: '', operators: ['eq', 'ne'] },
|
|
69
|
+
'textarea': { defaultOperator: 'contains', defaultValue: '', operators: ['eq', 'ne', 'contains', 'startswith', 'endswith'] }
|
|
70
|
+
};
|
|
42
71
|
buildQueryUIFields(fields) {
|
|
43
72
|
return [
|
|
73
|
+
// {
|
|
74
|
+
// key: 'quickSearch',
|
|
75
|
+
// type: 'input',
|
|
76
|
+
// props: {
|
|
77
|
+
// label: 'بحث سريع',
|
|
78
|
+
// placeholder: 'ابحث في جميع الحقول...',
|
|
79
|
+
// icon: 'pi pi-search'
|
|
80
|
+
// }
|
|
81
|
+
// },
|
|
44
82
|
{
|
|
45
|
-
|
|
46
|
-
|
|
83
|
+
type: 'tab-type',
|
|
84
|
+
// key: 'queryTabs',
|
|
47
85
|
props: {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
86
|
+
tabViewClass: 'advanced-query-tabs',
|
|
87
|
+
scrollable: false,
|
|
88
|
+
lazy: true,
|
|
89
|
+
activeIndex: 0
|
|
90
|
+
},
|
|
91
|
+
fieldGroup: this.getQueryTabs(fields)
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
getQueryTabs(fields) {
|
|
96
|
+
return [
|
|
97
|
+
{
|
|
98
|
+
type: 'group',
|
|
99
|
+
props: {
|
|
100
|
+
label: 'FILTERS',
|
|
101
|
+
leftIcon: 'pi pi-filter',
|
|
102
|
+
badge: (field) => {
|
|
103
|
+
const fieldGroup = (field?.fieldGroup ?? [])[0];
|
|
104
|
+
return (fieldGroup?.model?.advancedFilters ?? [])[0]?.conditions?.length || 0;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
fieldGroup: this.getFilterTabFields(fields)
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: 'group',
|
|
111
|
+
props: {
|
|
112
|
+
label: 'SORTING',
|
|
113
|
+
leftIcon: 'pi pi-sort-alt',
|
|
114
|
+
badge: (field) => {
|
|
115
|
+
const fieldGroup = (field?.fieldGroup ?? [])[0];
|
|
116
|
+
return (fieldGroup?.model?.sorting ?? [])?.length || 0;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
fieldGroup: this.getSortingTabFields(fields)
|
|
52
120
|
},
|
|
121
|
+
{
|
|
122
|
+
type: 'group',
|
|
123
|
+
props: {
|
|
124
|
+
label: 'PAGINATION',
|
|
125
|
+
leftIcon: 'pi pi-table',
|
|
126
|
+
badge: (field) => {
|
|
127
|
+
return field?.model?.pagination && field?.model?.pagination?.top ? 1 : 0;
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
fieldGroup: this.getPaginationTabFields()
|
|
131
|
+
}
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
getFilterTabFields(fields) {
|
|
135
|
+
return [
|
|
53
136
|
{
|
|
54
137
|
key: 'advancedFilters',
|
|
55
138
|
type: 'query-builder',
|
|
56
139
|
props: {
|
|
57
140
|
label: 'مرشحات متقدمة',
|
|
141
|
+
description: 'أنشئ مرشحات معقدة باستخدام مشغلين منطقيين',
|
|
58
142
|
fields: this.extractFilterableFields(fields)
|
|
59
143
|
}
|
|
60
|
-
}
|
|
144
|
+
}
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
getSortingTabFields(fields) {
|
|
148
|
+
return [
|
|
61
149
|
{
|
|
62
150
|
key: 'sorting',
|
|
63
151
|
type: 'sort-builder',
|
|
64
152
|
props: {
|
|
65
153
|
label: 'ترتيب النتائج',
|
|
154
|
+
description: 'حدد أولوية وعرض ترتيب البيانات',
|
|
66
155
|
fields: this.extractSortableFields(fields)
|
|
67
156
|
}
|
|
68
|
-
}
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
getPaginationTabFields() {
|
|
161
|
+
return [
|
|
69
162
|
{
|
|
70
163
|
key: 'pagination',
|
|
71
164
|
type: 'group',
|
|
72
|
-
props: {
|
|
165
|
+
props: {
|
|
166
|
+
label: '',
|
|
167
|
+
description: '',
|
|
168
|
+
collapsible: false,
|
|
169
|
+
showFieldCount: true,
|
|
170
|
+
variant: 'card'
|
|
171
|
+
},
|
|
73
172
|
fieldGroup: [
|
|
74
173
|
{
|
|
75
174
|
key: 'top',
|
|
76
175
|
type: 'input',
|
|
77
176
|
props: {
|
|
78
177
|
type: 'number',
|
|
79
|
-
label: '
|
|
178
|
+
label: 'الحد الأقصى للسجلات',
|
|
80
179
|
min: 1,
|
|
81
180
|
max: 1000,
|
|
82
181
|
description: 'أقصى عدد للسجلات المراد عرضها'
|
|
83
182
|
}
|
|
84
183
|
},
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
184
|
+
// {
|
|
185
|
+
// key: 'skip',
|
|
186
|
+
// type: 'input',
|
|
187
|
+
// props: {
|
|
188
|
+
// type: 'number',
|
|
189
|
+
// label: 'تخطي السجلات',
|
|
190
|
+
// min: 0,
|
|
191
|
+
// description: 'عدد السجلات التي سيتم تخطيها'
|
|
192
|
+
// }
|
|
193
|
+
// }
|
|
95
194
|
]
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
key: 'expand',
|
|
99
|
-
type: 'multiselect',
|
|
100
|
-
props: {
|
|
101
|
-
label: 'توسيع الحقول',
|
|
102
|
-
options: this.extractExpandableFields(fields),
|
|
103
|
-
placeholder: 'اختر الحقول للتوسيع'
|
|
104
|
-
}
|
|
105
195
|
}
|
|
106
196
|
];
|
|
107
197
|
}
|
|
198
|
+
getFilterCount() {
|
|
199
|
+
return '0';
|
|
200
|
+
}
|
|
108
201
|
extractFilterableFields(fields) {
|
|
109
202
|
const filterableFields = [];
|
|
110
203
|
this.traverseFields(fields, (field) => {
|
|
111
204
|
if (field.key && field.props && field.props['filterable'] !== false && !field.props?.hidden) {
|
|
205
|
+
const fieldType = this.mapFieldType(field.type);
|
|
206
|
+
const fieldConfig = this.fieldTypeConfig[fieldType] || this.fieldTypeConfig.input;
|
|
112
207
|
filterableFields.push({
|
|
113
208
|
key: field.key,
|
|
114
|
-
label: field.props.label || field.key,
|
|
115
|
-
type:
|
|
116
|
-
|
|
209
|
+
label: this.translate.instant(field.props.label || field.key),
|
|
210
|
+
type: fieldType,
|
|
211
|
+
props: { ...field.props },
|
|
212
|
+
operators: fieldConfig.operators,
|
|
213
|
+
// Add default values for QueryBuilderComponent compatibility
|
|
214
|
+
defaultOperator: fieldConfig.defaultOperator,
|
|
215
|
+
defaultValue: fieldConfig.defaultValue
|
|
117
216
|
});
|
|
118
217
|
}
|
|
119
218
|
});
|
|
@@ -125,7 +224,7 @@ class QueryBuilderService {
|
|
|
125
224
|
if (field.key && field.props && field.props['sortable'] !== false && !field.props?.hidden) {
|
|
126
225
|
sortableFields.push({
|
|
127
226
|
key: field.key,
|
|
128
|
-
label: field.props.label || field.key
|
|
227
|
+
label: this.translate.instant(field.props.label || field.key)
|
|
129
228
|
});
|
|
130
229
|
}
|
|
131
230
|
});
|
|
@@ -149,32 +248,28 @@ class QueryBuilderService {
|
|
|
149
248
|
if (field.fieldGroup) {
|
|
150
249
|
this.traverseFields(field.fieldGroup, callback);
|
|
151
250
|
}
|
|
152
|
-
if (field.fieldGroup) {
|
|
153
|
-
this.traverseFields(field.fieldGroup ?? [], callback);
|
|
154
|
-
}
|
|
155
251
|
});
|
|
156
252
|
}
|
|
157
253
|
mapFieldType(type) {
|
|
158
254
|
const typeMap = {
|
|
159
|
-
'input': '
|
|
255
|
+
'input': 'input',
|
|
160
256
|
'number': 'number',
|
|
161
|
-
'datepicker': '
|
|
162
|
-
'select': '
|
|
163
|
-
'checkbox': '
|
|
164
|
-
'
|
|
165
|
-
'
|
|
257
|
+
'datepicker': 'datepicker',
|
|
258
|
+
'select': 'select',
|
|
259
|
+
'checkbox': 'checkbox',
|
|
260
|
+
'generic-selector': 'generic-selector',
|
|
261
|
+
'switch': 'switch',
|
|
262
|
+
'radio': 'radio',
|
|
263
|
+
'textarea': 'textarea'
|
|
166
264
|
};
|
|
167
|
-
return typeMap[type || 'input'] || '
|
|
265
|
+
return typeMap[type || 'input'] || 'input';
|
|
168
266
|
}
|
|
169
267
|
getOperatorsForType(type) {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
'date': ['eq', 'ne', 'gt', 'ge', 'lt', 'le'],
|
|
174
|
-
'boolean': ['eq', 'ne']
|
|
175
|
-
};
|
|
176
|
-
return operatorsMap[this.mapFieldType(type)] || ['eq', 'ne'];
|
|
268
|
+
const fieldType = this.mapFieldType(type);
|
|
269
|
+
const config = this.fieldTypeConfig[fieldType];
|
|
270
|
+
return config ? config.operators : ['eq', 'ne'];
|
|
177
271
|
}
|
|
272
|
+
// OData methods remain the same
|
|
178
273
|
buildODataFromQueryModel(model, originalFields) {
|
|
179
274
|
const params = {
|
|
180
275
|
filters: [],
|
|
@@ -185,38 +280,30 @@ class QueryBuilderService {
|
|
|
185
280
|
// Quick search
|
|
186
281
|
if (model.quickSearch) {
|
|
187
282
|
const searchableFields = this.extractFilterableFields(originalFields);
|
|
188
|
-
searchableFields.
|
|
283
|
+
const quickSearchConditions = searchableFields.map(field => ({
|
|
284
|
+
field: field.key,
|
|
285
|
+
operator: 'contains',
|
|
286
|
+
value: model.quickSearch
|
|
287
|
+
}));
|
|
288
|
+
if (quickSearchConditions.length > 0) {
|
|
189
289
|
params.filters.push({
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
value: model.quickSearch,
|
|
193
|
-
logicalOperator: 'or'
|
|
290
|
+
logicalOperator: 'or',
|
|
291
|
+
conditions: quickSearchConditions
|
|
194
292
|
});
|
|
195
|
-
}
|
|
293
|
+
}
|
|
196
294
|
}
|
|
197
295
|
// Advanced filters
|
|
198
296
|
if (model.advancedFilters && Array.isArray(model.advancedFilters)) {
|
|
199
|
-
model.advancedFilters
|
|
200
|
-
if (filter.field && filter.operator && filter.value !== undefined && filter.value !== '') {
|
|
201
|
-
params.filters.push({
|
|
202
|
-
field: filter.field,
|
|
203
|
-
operator: filter.operator,
|
|
204
|
-
value: filter.value,
|
|
205
|
-
logicalOperator: 'and'
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
});
|
|
297
|
+
params.filters.push(...model.advancedFilters);
|
|
209
298
|
}
|
|
210
299
|
// Sorting
|
|
211
300
|
if (model.sorting && Array.isArray(model.sorting)) {
|
|
212
|
-
model.sorting
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
});
|
|
301
|
+
params.orderBy = model.sorting
|
|
302
|
+
.filter((sort) => sort.field && sort.direction)
|
|
303
|
+
.map((sort) => ({
|
|
304
|
+
field: sort.field,
|
|
305
|
+
direction: sort.direction
|
|
306
|
+
}));
|
|
220
307
|
}
|
|
221
308
|
// Pagination
|
|
222
309
|
if (model.pagination) {
|
|
@@ -230,60 +317,64 @@ class QueryBuilderService {
|
|
|
230
317
|
return params;
|
|
231
318
|
}
|
|
232
319
|
toODataQueryString(params) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
320
|
+
return QueryParser.toString(params);
|
|
321
|
+
}
|
|
322
|
+
parseODataQuery(queryString) {
|
|
323
|
+
return QueryParser.parse(queryString);
|
|
324
|
+
}
|
|
325
|
+
parseODataToQueryModel(queryString, originalFields) {
|
|
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
|
+
}
|
|
253
341
|
}
|
|
342
|
+
group.conditions.forEach(condition => {
|
|
343
|
+
model.advancedFilters.push({
|
|
344
|
+
field: condition.field,
|
|
345
|
+
operator: condition.operator,
|
|
346
|
+
value: condition.value
|
|
347
|
+
});
|
|
348
|
+
});
|
|
254
349
|
});
|
|
255
|
-
queryParts.push(`$filter=${encodeURIComponent(filterString)}`);
|
|
256
350
|
}
|
|
257
|
-
//
|
|
258
|
-
if (params.orderBy
|
|
259
|
-
|
|
260
|
-
|
|
351
|
+
// Parse sorting
|
|
352
|
+
if (params.orderBy) {
|
|
353
|
+
model.sorting = params.orderBy.map(order => ({
|
|
354
|
+
field: order.field,
|
|
355
|
+
direction: order.direction
|
|
356
|
+
}));
|
|
261
357
|
}
|
|
262
|
-
//
|
|
263
|
-
if (params.
|
|
264
|
-
|
|
358
|
+
// Parse pagination
|
|
359
|
+
if (params.top !== undefined) {
|
|
360
|
+
model.pagination.top = params.top;
|
|
265
361
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
queryParts.push(`$top=${params.top}`);
|
|
362
|
+
if (params.skip !== undefined) {
|
|
363
|
+
model.pagination.skip = params.skip;
|
|
269
364
|
}
|
|
270
|
-
//
|
|
271
|
-
if (params.
|
|
272
|
-
|
|
365
|
+
// Parse expand
|
|
366
|
+
if (params.expand) {
|
|
367
|
+
model.expand = params.expand;
|
|
273
368
|
}
|
|
274
|
-
return
|
|
369
|
+
return model;
|
|
275
370
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
else if (typeof value === 'boolean') {
|
|
284
|
-
return value.toString();
|
|
285
|
-
}
|
|
286
|
-
return value;
|
|
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
|
|
376
|
+
getFieldConfig(fieldKey, fields) {
|
|
377
|
+
return fields.find(f => f.key === fieldKey);
|
|
287
378
|
}
|
|
288
379
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: QueryBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
289
380
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: QueryBuilderService, providedIn: 'root' });
|
|
@@ -295,248 +386,135 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImpor
|
|
|
295
386
|
}]
|
|
296
387
|
}] });
|
|
297
388
|
|
|
298
|
-
//
|
|
299
|
-
class
|
|
300
|
-
|
|
389
|
+
// generic-search.component.ts
|
|
390
|
+
class GenericSearchAdvanced {
|
|
391
|
+
translate = inject(TranslateService);
|
|
392
|
+
queryBuilder = inject(QueryBuilderService);
|
|
393
|
+
drawerVisible = false;
|
|
394
|
+
search = new EventEmitter();
|
|
395
|
+
paginationChange = new EventEmitter();
|
|
396
|
+
odataSearch = new EventEmitter();
|
|
397
|
+
form = new FormGroup({});
|
|
398
|
+
model = {};
|
|
399
|
+
options = {};
|
|
400
|
+
fields = [];
|
|
401
|
+
fields_ = [];
|
|
402
|
+
enableQueryBuilder = true;
|
|
403
|
+
odataConfig = {};
|
|
301
404
|
ngOnInit() {
|
|
302
|
-
|
|
303
|
-
this.conditions = [...this.formControl.value];
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
addCondition() {
|
|
307
|
-
this.conditions.push({ field: '', operator: 'eq', value: '' });
|
|
308
|
-
this.updateValue();
|
|
309
|
-
}
|
|
310
|
-
removeCondition(index) {
|
|
311
|
-
this.conditions.splice(index, 1);
|
|
312
|
-
this.updateValue();
|
|
405
|
+
this.buildQueryUIFields();
|
|
313
406
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
407
|
+
get filterCount() {
|
|
408
|
+
const filters = ((this.model?.advancedFilters ?? [])[0]?.conditions?.length || 0) +
|
|
409
|
+
((this.model?.sorting ?? [])?.length || 0) +
|
|
410
|
+
(this.model?.pagination && this.model?.pagination?.top ? 1 : 0);
|
|
411
|
+
return filters;
|
|
318
412
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const operators = field?.operators || ['eq', 'ne'];
|
|
327
|
-
return operators.map((op) => ({
|
|
328
|
-
label: this.getOperatorLabel(op),
|
|
329
|
-
value: op
|
|
330
|
-
}));
|
|
413
|
+
buildQueryUIFields() {
|
|
414
|
+
if (this.enableQueryBuilder) {
|
|
415
|
+
this.fields_ = this.queryBuilder.buildQueryUIFields(this.fields);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
this.fields_ = this.fields;
|
|
419
|
+
}
|
|
331
420
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
421
|
+
onSubmit() {
|
|
422
|
+
// Use the new QueryBuilderService methods
|
|
423
|
+
const odataParams = this.queryBuilder.buildODataFromQueryModel(this.model, this.fields);
|
|
424
|
+
// this.odataSearch.emit(odataParams);
|
|
425
|
+
// Generate query string using the unified method
|
|
426
|
+
const queryString = this.queryBuilder.toODataQueryString(odataParams);
|
|
427
|
+
// console.log('OData Query String:', queryString);
|
|
428
|
+
debugger;
|
|
429
|
+
if (this.model?.pagination && this.model?.pagination?.top) {
|
|
430
|
+
this.paginationChange.emit({
|
|
431
|
+
top: this.model?.pagination?.top,
|
|
432
|
+
// skip:,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
this.search.emit({
|
|
436
|
+
filter: this.model['filter'],
|
|
437
|
+
query: queryString,
|
|
438
|
+
// sorting:,
|
|
439
|
+
// top:,
|
|
440
|
+
// skip:,
|
|
441
|
+
});
|
|
345
442
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
443
|
+
onReset() {
|
|
444
|
+
this.model = {};
|
|
445
|
+
this.form.reset();
|
|
446
|
+
this.paginationChange.emit({
|
|
447
|
+
top: 10,
|
|
448
|
+
// skip:,
|
|
449
|
+
});
|
|
450
|
+
this.search.emit(this.model);
|
|
451
|
+
// Emit empty QueryParameters using the proper structure
|
|
452
|
+
// this.odataSearch.emit({
|
|
453
|
+
// filters: [],
|
|
454
|
+
// orderBy: [],
|
|
455
|
+
// groupBy: [],
|
|
456
|
+
// expand: []
|
|
457
|
+
// });
|
|
349
458
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
459
|
+
// Toggle drawer visibility
|
|
460
|
+
toggleDrawer() {
|
|
461
|
+
this.drawerVisible = !this.drawerVisible;
|
|
353
462
|
}
|
|
354
|
-
|
|
355
|
-
|
|
463
|
+
// Close drawer
|
|
464
|
+
closeDrawer() {
|
|
465
|
+
this.drawerVisible = false;
|
|
356
466
|
}
|
|
357
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type:
|
|
358
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
359
|
-
<div class="query-builder p-3 border-round border-1 surface-border">
|
|
360
|
-
<h4 class="mt-0">{{ props.label }}</h4>
|
|
361
|
-
|
|
362
|
-
<div *ngFor="let condition of conditions; let i = index" class="condition-row mb-2 p-2 border-round surface-card">
|
|
363
|
-
<div class="grid grid-nogutter align-items-center gap-2">
|
|
364
|
-
<!-- Field Selector -->
|
|
365
|
-
<div class="col">
|
|
366
|
-
<p-select
|
|
367
|
-
[options]="props['fields']"
|
|
368
|
-
optionLabel="label"
|
|
369
|
-
optionValue="key"
|
|
370
|
-
[(ngModel)]="condition.field"
|
|
371
|
-
(onChange)="onFieldChange(condition)"
|
|
372
|
-
placeholder="اختر الحقل"
|
|
373
|
-
[showClear]="true"
|
|
374
|
-
styleClass="w-full">
|
|
375
|
-
</p-select>
|
|
376
|
-
</div>
|
|
377
|
-
|
|
378
|
-
<!-- Operator Selector -->
|
|
379
|
-
<div class="col">
|
|
380
|
-
<p-select
|
|
381
|
-
[options]="getOperatorsForField(condition.field)"
|
|
382
|
-
[(ngModel)]="condition.operator"
|
|
383
|
-
placeholder="المعامل"
|
|
384
|
-
styleClass="w-full">
|
|
385
|
-
</p-select>
|
|
386
|
-
</div>
|
|
387
|
-
|
|
388
|
-
<!-- Value Input -->
|
|
389
|
-
<div class="col" *ngIf="condition.field && getFieldType(condition.field) !== 'boolean'">
|
|
390
|
-
<input
|
|
391
|
-
*ngIf="getFieldType(condition.field) !== 'date'"
|
|
392
|
-
pInputText
|
|
393
|
-
[(ngModel)]="condition.value"
|
|
394
|
-
[type]="getInputType(condition.field)"
|
|
395
|
-
[placeholder]="getValuePlaceholder(condition.field)"
|
|
396
|
-
styleClass="w-full">
|
|
397
|
-
|
|
398
|
-
<p-calendar
|
|
399
|
-
*ngIf="getFieldType(condition.field) === 'date'"
|
|
400
|
-
[(ngModel)]="condition.value"
|
|
401
|
-
dateFormat="yy-mm-dd"
|
|
402
|
-
styleClass="w-full">
|
|
403
|
-
</p-calendar>
|
|
404
|
-
</div>
|
|
405
|
-
|
|
406
|
-
<!-- Boolean Value -->
|
|
407
|
-
<div class="col" *ngIf="condition.field && getFieldType(condition.field) === 'boolean'">
|
|
408
|
-
<p-checkbox
|
|
409
|
-
[(ngModel)]="condition.value"
|
|
410
|
-
[binary]="true">
|
|
411
|
-
</p-checkbox>
|
|
412
|
-
</div>
|
|
413
|
-
|
|
414
|
-
<!-- Remove Button -->
|
|
415
|
-
<div class="col-fixed">
|
|
416
|
-
<button
|
|
417
|
-
pButton
|
|
418
|
-
icon="pi pi-times"
|
|
419
|
-
class="p-button-danger p-button-text"
|
|
420
|
-
(click)="removeCondition(i)">
|
|
421
|
-
</button>
|
|
422
|
-
</div>
|
|
423
|
-
</div>
|
|
424
|
-
</div>
|
|
425
|
-
|
|
426
|
-
<button
|
|
427
|
-
pButton
|
|
428
|
-
icon="pi pi-plus"
|
|
429
|
-
label="إضافة شرط"
|
|
430
|
-
class="p-button-outlined w-full"
|
|
431
|
-
(click)="addCondition()">
|
|
432
|
-
</button>
|
|
433
|
-
</div>
|
|
434
|
-
`, 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.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.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: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i6.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"] }] });
|
|
467
|
+
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", enableQueryBuilder: "enableQueryBuilder", 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: i4$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4$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: i4$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4$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" }] });
|
|
435
469
|
}
|
|
436
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type:
|
|
470
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GenericSearchAdvanced, decorators: [{
|
|
437
471
|
type: Component,
|
|
438
|
-
args: [{
|
|
439
|
-
selector: 'formly-query-builder',
|
|
440
|
-
standalone: true,
|
|
441
|
-
imports: [
|
|
472
|
+
args: [{ selector: 'lib-generic-search-advanced', standalone: true, imports: [
|
|
442
473
|
CommonModule,
|
|
474
|
+
FormlyForm,
|
|
475
|
+
TranslateModule,
|
|
476
|
+
ToolbarModule,
|
|
477
|
+
ButtonModule,
|
|
478
|
+
ReactiveFormsModule,
|
|
443
479
|
FormsModule,
|
|
444
|
-
|
|
480
|
+
InputIconModule,
|
|
481
|
+
IconFieldModule,
|
|
445
482
|
InputTextModule,
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
template:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
styleClass="w-full">
|
|
467
|
-
</p-select>
|
|
468
|
-
</div>
|
|
469
|
-
|
|
470
|
-
<!-- Operator Selector -->
|
|
471
|
-
<div class="col">
|
|
472
|
-
<p-select
|
|
473
|
-
[options]="getOperatorsForField(condition.field)"
|
|
474
|
-
[(ngModel)]="condition.operator"
|
|
475
|
-
placeholder="المعامل"
|
|
476
|
-
styleClass="w-full">
|
|
477
|
-
</p-select>
|
|
478
|
-
</div>
|
|
479
|
-
|
|
480
|
-
<!-- Value Input -->
|
|
481
|
-
<div class="col" *ngIf="condition.field && getFieldType(condition.field) !== 'boolean'">
|
|
482
|
-
<input
|
|
483
|
-
*ngIf="getFieldType(condition.field) !== 'date'"
|
|
484
|
-
pInputText
|
|
485
|
-
[(ngModel)]="condition.value"
|
|
486
|
-
[type]="getInputType(condition.field)"
|
|
487
|
-
[placeholder]="getValuePlaceholder(condition.field)"
|
|
488
|
-
styleClass="w-full">
|
|
489
|
-
|
|
490
|
-
<p-calendar
|
|
491
|
-
*ngIf="getFieldType(condition.field) === 'date'"
|
|
492
|
-
[(ngModel)]="condition.value"
|
|
493
|
-
dateFormat="yy-mm-dd"
|
|
494
|
-
styleClass="w-full">
|
|
495
|
-
</p-calendar>
|
|
496
|
-
</div>
|
|
497
|
-
|
|
498
|
-
<!-- Boolean Value -->
|
|
499
|
-
<div class="col" *ngIf="condition.field && getFieldType(condition.field) === 'boolean'">
|
|
500
|
-
<p-checkbox
|
|
501
|
-
[(ngModel)]="condition.value"
|
|
502
|
-
[binary]="true">
|
|
503
|
-
</p-checkbox>
|
|
504
|
-
</div>
|
|
505
|
-
|
|
506
|
-
<!-- Remove Button -->
|
|
507
|
-
<div class="col-fixed">
|
|
508
|
-
<button
|
|
509
|
-
pButton
|
|
510
|
-
icon="pi pi-times"
|
|
511
|
-
class="p-button-danger p-button-text"
|
|
512
|
-
(click)="removeCondition(i)">
|
|
513
|
-
</button>
|
|
514
|
-
</div>
|
|
515
|
-
</div>
|
|
516
|
-
</div>
|
|
517
|
-
|
|
518
|
-
<button
|
|
519
|
-
pButton
|
|
520
|
-
icon="pi pi-plus"
|
|
521
|
-
label="إضافة شرط"
|
|
522
|
-
class="p-button-outlined w-full"
|
|
523
|
-
(click)="addCondition()">
|
|
524
|
-
</button>
|
|
525
|
-
</div>
|
|
526
|
-
`
|
|
527
|
-
}]
|
|
528
|
-
}] });
|
|
483
|
+
DrawerModule,
|
|
484
|
+
InputGroupModule,
|
|
485
|
+
InputGroupAddonModule,
|
|
486
|
+
BadgeModule
|
|
487
|
+
], 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" }]
|
|
488
|
+
}], propDecorators: { search: [{
|
|
489
|
+
type: Output
|
|
490
|
+
}], paginationChange: [{
|
|
491
|
+
type: Output
|
|
492
|
+
}], odataSearch: [{
|
|
493
|
+
type: Output
|
|
494
|
+
}], model: [{
|
|
495
|
+
type: Input
|
|
496
|
+
}], fields: [{
|
|
497
|
+
type: Input
|
|
498
|
+
}], enableQueryBuilder: [{
|
|
499
|
+
type: Input
|
|
500
|
+
}], odataConfig: [{
|
|
501
|
+
type: Input
|
|
502
|
+
}] } });
|
|
529
503
|
|
|
530
504
|
// sort-builder.component.ts
|
|
531
505
|
class SortBuilderComponent extends FieldType {
|
|
532
506
|
sorts = [];
|
|
533
507
|
directionOptions = [
|
|
534
|
-
{ label: 'تصاعدي', value: 'asc' },
|
|
535
|
-
{ label: 'تنازلي', value: 'desc' }
|
|
508
|
+
{ label: 'تصاعدي (أ → ي)', value: 'asc' },
|
|
509
|
+
{ label: 'تنازلي (ي → أ)', value: 'desc' }
|
|
536
510
|
];
|
|
511
|
+
dragStartIndex = -1;
|
|
537
512
|
ngOnInit() {
|
|
538
513
|
if (this.formControl.value && Array.isArray(this.formControl.value)) {
|
|
539
|
-
this.sorts =
|
|
514
|
+
this.sorts = this.formControl.value.map((sort) => ({
|
|
515
|
+
field: sort.field || '',
|
|
516
|
+
direction: sort.direction || 'asc'
|
|
517
|
+
}));
|
|
540
518
|
}
|
|
541
519
|
}
|
|
542
520
|
addSort() {
|
|
@@ -547,262 +525,1059 @@ class SortBuilderComponent extends FieldType {
|
|
|
547
525
|
this.sorts.splice(index, 1);
|
|
548
526
|
this.updateValue();
|
|
549
527
|
}
|
|
528
|
+
moveSort(fromIndex, toIndex) {
|
|
529
|
+
if (toIndex >= 0 && toIndex < this.sorts.length) {
|
|
530
|
+
const movedItem = this.sorts.splice(fromIndex, 1)[0];
|
|
531
|
+
this.sorts.splice(toIndex, 0, movedItem);
|
|
532
|
+
this.updateValue();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
onDragStart(index) {
|
|
536
|
+
this.dragStartIndex = index;
|
|
537
|
+
}
|
|
538
|
+
onDrop(event, dropIndex) {
|
|
539
|
+
if (this.dragStartIndex !== -1 && this.dragStartIndex !== dropIndex) {
|
|
540
|
+
this.moveSort(this.dragStartIndex, dropIndex);
|
|
541
|
+
}
|
|
542
|
+
this.dragStartIndex = -1;
|
|
543
|
+
}
|
|
544
|
+
reverseSorts() {
|
|
545
|
+
this.sorts.forEach(sort => {
|
|
546
|
+
sort.direction = sort.direction === 'asc' ? 'desc' : 'asc';
|
|
547
|
+
});
|
|
548
|
+
this.updateValue();
|
|
549
|
+
}
|
|
550
|
+
clearAll() {
|
|
551
|
+
this.sorts = [];
|
|
552
|
+
this.updateValue();
|
|
553
|
+
}
|
|
554
|
+
getAvailableFields(currentField) {
|
|
555
|
+
const usedFields = this.sorts.map(s => s.field).filter(f => f && f !== currentField);
|
|
556
|
+
return this.props['fields'].filter((field) => !usedFields.includes(field.key) || field.key === currentField);
|
|
557
|
+
}
|
|
558
|
+
getFieldLabel(fieldKey) {
|
|
559
|
+
const field = this.props['fields'].find((f) => f.key === fieldKey);
|
|
560
|
+
return field?.label || fieldKey;
|
|
561
|
+
}
|
|
562
|
+
getDirectionIcon(direction) {
|
|
563
|
+
return direction === 'asc' ? 'pi pi-sort-up' : 'pi pi-sort-down';
|
|
564
|
+
}
|
|
565
|
+
getDirectionColor(direction) {
|
|
566
|
+
return direction === 'asc' ? 'text-green-500' : 'text-red-500';
|
|
567
|
+
}
|
|
550
568
|
updateValue() {
|
|
551
|
-
|
|
569
|
+
// Filter out empty sorts and update value
|
|
570
|
+
const validSorts = this.sorts.filter(sort => sort.field && sort.direction);
|
|
571
|
+
this.formControl.setValue(validSorts);
|
|
572
|
+
}
|
|
573
|
+
// Helper to check if it's the last item
|
|
574
|
+
get isLast() {
|
|
575
|
+
return false; // This is used in template context
|
|
576
|
+
}
|
|
577
|
+
// Method to get current sort configuration
|
|
578
|
+
getSortConfiguration() {
|
|
579
|
+
return this.sorts.filter(sort => sort.field && sort.direction);
|
|
580
|
+
}
|
|
581
|
+
// Method to load sort configuration
|
|
582
|
+
loadSortConfiguration(sortConfig) {
|
|
583
|
+
if (sortConfig && Array.isArray(sortConfig)) {
|
|
584
|
+
this.sorts = sortConfig.map(sort => ({
|
|
585
|
+
field: sort.field || '',
|
|
586
|
+
direction: sort.direction || 'asc'
|
|
587
|
+
}));
|
|
588
|
+
this.updateValue();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
// Method to check if a field is already used in sorting
|
|
592
|
+
isFieldUsed(fieldKey) {
|
|
593
|
+
return this.sorts.some(sort => sort.field === fieldKey);
|
|
594
|
+
}
|
|
595
|
+
// Get the next available field
|
|
596
|
+
getNextAvailableField() {
|
|
597
|
+
const availableFields = this.props['fields'].filter((field) => !this.isFieldUsed(field.key));
|
|
598
|
+
return availableFields.length > 0 ? availableFields[0].key : '';
|
|
552
599
|
}
|
|
553
600
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: SortBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
554
601
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.8", type: SortBuilderComponent, isStandalone: true, selector: "formly-sort-builder", usesInheritance: true, ngImport: i0, template: `
|
|
555
602
|
<div class="sort-builder p-3 border-round border-1 surface-border">
|
|
556
|
-
<
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
<!-- Field Selector -->
|
|
561
|
-
<div class="col">
|
|
562
|
-
<p-select
|
|
563
|
-
[options]="props['fields']"
|
|
564
|
-
optionLabel="label"
|
|
565
|
-
optionValue="key"
|
|
566
|
-
[(ngModel)]="sort.field"
|
|
567
|
-
placeholder="اختر الحقل للترتيب"
|
|
568
|
-
[showClear]="true"
|
|
569
|
-
styleClass="w-full">
|
|
570
|
-
</p-select>
|
|
571
|
-
</div>
|
|
603
|
+
<div class="flex justify-content-between align-items-center mb-3">
|
|
604
|
+
<h4 class="mt-0 mb-0">{{ props.label }}</h4>
|
|
605
|
+
<small class="text-500">الأولوية من الأعلى إلى الأسفل</small>
|
|
606
|
+
</div>
|
|
572
607
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
608
|
+
<!-- Sort List -->
|
|
609
|
+
<div *ngIf="sorts.length > 0" class="sort-list space-y-2 mb-3">
|
|
610
|
+
<div
|
|
611
|
+
*ngFor="let sort of sorts; let i = index; let isFirst = first; let isLast = last"
|
|
612
|
+
class="sort-row p-2 border-round surface-card"
|
|
613
|
+
pDraggable
|
|
614
|
+
pDroppable
|
|
615
|
+
(onDragStart)="onDragStart(i)"
|
|
616
|
+
(onDrop)="onDrop($event, i)"
|
|
617
|
+
[ngClass]="{'border-left-3 border-primary': isFirst, 'border-left-3 border-200': !isFirst}">
|
|
618
|
+
|
|
619
|
+
<div class="grid grid-cols-4 align-items-center gap-2">
|
|
620
|
+
<!-- Drag Handle -->
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
<!-- Field Selector -->
|
|
624
|
+
<div class="col-span-2 flex">
|
|
625
|
+
<button
|
|
626
|
+
pButton
|
|
627
|
+
icon="pi pi-bars"
|
|
628
|
+
type="button"
|
|
629
|
+
class="p-button-text p-button-plain cursor-move"
|
|
630
|
+
pTooltip="اسحب لتغيير الأولوية"
|
|
631
|
+
tooltipPosition="top">
|
|
632
|
+
</button>
|
|
633
|
+
<p-select
|
|
634
|
+
[options]="getAvailableFields(sort.field)"
|
|
635
|
+
optionLabel="label"
|
|
636
|
+
optionValue="key"
|
|
637
|
+
[(ngModel)]="sort.field"
|
|
638
|
+
(onChange)="updateValue()"
|
|
639
|
+
placeholder="اختر الحقل للترتيب"
|
|
640
|
+
[showClear]="true"
|
|
641
|
+
appendTo="body"
|
|
642
|
+
styleClass="w-full">
|
|
643
|
+
</p-select>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
<!-- Direction Icon -->
|
|
648
|
+
<div class="col-fixed">
|
|
649
|
+
<p-select
|
|
650
|
+
[options]="directionOptions"
|
|
651
|
+
[(ngModel)]="sort.direction"
|
|
652
|
+
(onChange)="updateValue()"
|
|
653
|
+
placeholder="الاتجاه"
|
|
654
|
+
appendTo="body"
|
|
655
|
+
styleClass="w-full">
|
|
656
|
+
</p-select>
|
|
657
|
+
</div>
|
|
658
|
+
|
|
659
|
+
<!-- Actions -->
|
|
660
|
+
<div class="col-fixed flex gap-2" style="justify-content: flex-end;">
|
|
582
661
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
662
|
+
<!--
|
|
663
|
+
<button
|
|
664
|
+
outlined
|
|
665
|
+
pButton
|
|
666
|
+
[icon]="getDirectionIcon(sort.direction)"
|
|
667
|
+
type="button"
|
|
668
|
+
[ngClass]="'p-button-text p-button-sm' + getDirectionColor(sort.direction)"
|
|
669
|
+
pTooltip="{{ sort.direction === 'asc' ? 'تصاعدي' : 'تنازلي' }}"
|
|
670
|
+
tooltipPosition="top">
|
|
671
|
+
</button> -->
|
|
672
|
+
<!-- Move Up -->
|
|
673
|
+
<button
|
|
674
|
+
outlined
|
|
675
|
+
style=" width: var(--p-button-icon-only-width);"
|
|
676
|
+
*ngIf="!isFirst"
|
|
677
|
+
pButton
|
|
678
|
+
icon="pi pi-arrow-up"
|
|
679
|
+
type="button"
|
|
680
|
+
class=" p-button-sm p-button-success"
|
|
681
|
+
(click)="moveSort(i, i - 1)"
|
|
682
|
+
pTooltip="تحريك للأعلى"
|
|
683
|
+
tooltipPosition="top">
|
|
684
|
+
</button>
|
|
685
|
+
|
|
686
|
+
<!-- Move Down -->
|
|
687
|
+
<button
|
|
688
|
+
outlined
|
|
689
|
+
style=" width: var(--p-button-icon-only-width);"
|
|
690
|
+
*ngIf="!isLast"
|
|
691
|
+
pButton
|
|
692
|
+
icon="pi pi-arrow-down"
|
|
693
|
+
type="button"
|
|
694
|
+
class=" p-button-sm p-button-success"
|
|
695
|
+
(click)="moveSort(i, i + 1)"
|
|
696
|
+
pTooltip="تحريك للأسفل"
|
|
697
|
+
tooltipPosition="top">
|
|
698
|
+
</button>
|
|
699
|
+
|
|
700
|
+
<!-- Remove -->
|
|
701
|
+
<button
|
|
702
|
+
style=" width: var(--p-button-icon-only-width);"
|
|
703
|
+
pButton
|
|
704
|
+
icon="pi pi-times"
|
|
705
|
+
type="button"
|
|
706
|
+
class=" p-button-sm p-button-danger"
|
|
707
|
+
(click)="removeSort(i)"
|
|
708
|
+
pTooltip="حذف الترتيب"
|
|
709
|
+
tooltipPosition="top">
|
|
710
|
+
</button>
|
|
711
|
+
</div>
|
|
591
712
|
</div>
|
|
713
|
+
|
|
714
|
+
<!-- Field Info -->
|
|
715
|
+
<!-- <div *ngIf="sort.field" class="field-info mt-2 text-sm text-500">
|
|
716
|
+
<small>الحقل: {{ getFieldLabel(sort.field) }}</small>
|
|
717
|
+
</div> -->
|
|
592
718
|
</div>
|
|
593
719
|
</div>
|
|
594
720
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
class="
|
|
600
|
-
|
|
601
|
-
|
|
721
|
+
<!-- Empty State -->
|
|
722
|
+
<div *ngIf="sorts.length === 0" class="empty-state text-center p-4 border-round surface-section">
|
|
723
|
+
<i class="pi pi-sort-alt text-4xl text-500 mb-3"></i>
|
|
724
|
+
<p class="text-500 mb-3">لا توجد قواعد ترتيب محددة</p>
|
|
725
|
+
<p class="text-400 text-sm mb-3">سيتم استخدام الترتيب الافتراضي للبيانات</p>
|
|
726
|
+
</div>
|
|
727
|
+
|
|
728
|
+
<!-- Actions -->
|
|
729
|
+
<div class="flex gap-2">
|
|
730
|
+
<button
|
|
731
|
+
pButton
|
|
732
|
+
icon="pi pi-sort-alt"
|
|
733
|
+
label="إضافة ترتيب"
|
|
734
|
+
type="button"
|
|
735
|
+
class="p-button-outlined flex-1"
|
|
736
|
+
(click)="addSort()">
|
|
737
|
+
</button>
|
|
738
|
+
|
|
739
|
+
<button
|
|
740
|
+
*ngIf="sorts.length > 1"
|
|
741
|
+
pButton
|
|
742
|
+
icon="pi pi-random"
|
|
743
|
+
label="عكس الترتيب"
|
|
744
|
+
type="button"
|
|
745
|
+
class="p-button-outlined"
|
|
746
|
+
(click)="reverseSorts()"
|
|
747
|
+
pTooltip="عكس اتجاه جميع قواعد الترتيب"
|
|
748
|
+
tooltipPosition="top">
|
|
749
|
+
</button>
|
|
750
|
+
|
|
751
|
+
<button
|
|
752
|
+
*ngIf="sorts.length > 0"
|
|
753
|
+
pButton
|
|
754
|
+
icon="pi pi-trash"
|
|
755
|
+
type="button"
|
|
756
|
+
class="p-button-outlined p-button-danger"
|
|
757
|
+
(click)="clearAll()"
|
|
758
|
+
pTooltip="مسح جميع قواعد الترتيب"
|
|
759
|
+
tooltipPosition="top">
|
|
760
|
+
</button>
|
|
761
|
+
</div>
|
|
762
|
+
|
|
763
|
+
<!-- Summary -->
|
|
764
|
+
<div *ngIf="sorts.length > 0" class="sort-summary mt-3 p-2 border-round surface-ground">
|
|
765
|
+
<h5 class="mt-0 mb-2 text-sm">ملخص الترتيب:</h5>
|
|
766
|
+
<div class="text-sm text-500">
|
|
767
|
+
<span *ngFor="let sort of sorts; let i = index" class="sort-step">
|
|
768
|
+
<span class="font-semibold">{{ getFieldLabel(sort.field) }}</span>
|
|
769
|
+
<span class="direction-badge" [ngClass]="sort.direction === 'asc' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'">
|
|
770
|
+
{{ sort.direction === 'asc' ? '▲' : '▼' }}
|
|
771
|
+
</span>
|
|
772
|
+
<span *ngIf="!isLast"> ثم </span>
|
|
773
|
+
</span>
|
|
774
|
+
</div>
|
|
775
|
+
</div>
|
|
602
776
|
</div>
|
|
603
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type:
|
|
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: 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: 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"] }] });
|
|
604
778
|
}
|
|
605
779
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: SortBuilderComponent, decorators: [{
|
|
606
780
|
type: Component,
|
|
607
|
-
args: [{
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
781
|
+
args: [{ selector: 'formly-sort-builder', standalone: true, imports: [
|
|
782
|
+
CommonModule,
|
|
783
|
+
FormsModule,
|
|
784
|
+
SelectModule,
|
|
785
|
+
ButtonModule,
|
|
786
|
+
DragDropModule,
|
|
787
|
+
TooltipModule
|
|
788
|
+
], template: `
|
|
612
789
|
<div class="sort-builder p-3 border-round border-1 surface-border">
|
|
613
|
-
<
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
<!-- Field Selector -->
|
|
618
|
-
<div class="col">
|
|
619
|
-
<p-select
|
|
620
|
-
[options]="props['fields']"
|
|
621
|
-
optionLabel="label"
|
|
622
|
-
optionValue="key"
|
|
623
|
-
[(ngModel)]="sort.field"
|
|
624
|
-
placeholder="اختر الحقل للترتيب"
|
|
625
|
-
[showClear]="true"
|
|
626
|
-
styleClass="w-full">
|
|
627
|
-
</p-select>
|
|
628
|
-
</div>
|
|
790
|
+
<div class="flex justify-content-between align-items-center mb-3">
|
|
791
|
+
<h4 class="mt-0 mb-0">{{ props.label }}</h4>
|
|
792
|
+
<small class="text-500">الأولوية من الأعلى إلى الأسفل</small>
|
|
793
|
+
</div>
|
|
629
794
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
795
|
+
<!-- Sort List -->
|
|
796
|
+
<div *ngIf="sorts.length > 0" class="sort-list space-y-2 mb-3">
|
|
797
|
+
<div
|
|
798
|
+
*ngFor="let sort of sorts; let i = index; let isFirst = first; let isLast = last"
|
|
799
|
+
class="sort-row p-2 border-round surface-card"
|
|
800
|
+
pDraggable
|
|
801
|
+
pDroppable
|
|
802
|
+
(onDragStart)="onDragStart(i)"
|
|
803
|
+
(onDrop)="onDrop($event, i)"
|
|
804
|
+
[ngClass]="{'border-left-3 border-primary': isFirst, 'border-left-3 border-200': !isFirst}">
|
|
639
805
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
806
|
+
<div class="grid grid-cols-4 align-items-center gap-2">
|
|
807
|
+
<!-- Drag Handle -->
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
<!-- Field Selector -->
|
|
811
|
+
<div class="col-span-2 flex">
|
|
812
|
+
<button
|
|
813
|
+
pButton
|
|
814
|
+
icon="pi pi-bars"
|
|
815
|
+
type="button"
|
|
816
|
+
class="p-button-text p-button-plain cursor-move"
|
|
817
|
+
pTooltip="اسحب لتغيير الأولوية"
|
|
818
|
+
tooltipPosition="top">
|
|
819
|
+
</button>
|
|
820
|
+
<p-select
|
|
821
|
+
[options]="getAvailableFields(sort.field)"
|
|
822
|
+
optionLabel="label"
|
|
823
|
+
optionValue="key"
|
|
824
|
+
[(ngModel)]="sort.field"
|
|
825
|
+
(onChange)="updateValue()"
|
|
826
|
+
placeholder="اختر الحقل للترتيب"
|
|
827
|
+
[showClear]="true"
|
|
828
|
+
appendTo="body"
|
|
829
|
+
styleClass="w-full">
|
|
830
|
+
</p-select>
|
|
831
|
+
</div>
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
<!-- Direction Icon -->
|
|
835
|
+
<div class="col-fixed">
|
|
836
|
+
<p-select
|
|
837
|
+
[options]="directionOptions"
|
|
838
|
+
[(ngModel)]="sort.direction"
|
|
839
|
+
(onChange)="updateValue()"
|
|
840
|
+
placeholder="الاتجاه"
|
|
841
|
+
appendTo="body"
|
|
842
|
+
styleClass="w-full">
|
|
843
|
+
</p-select>
|
|
844
|
+
</div>
|
|
845
|
+
|
|
846
|
+
<!-- Actions -->
|
|
847
|
+
<div class="col-fixed flex gap-2" style="justify-content: flex-end;">
|
|
848
|
+
|
|
849
|
+
<!--
|
|
850
|
+
<button
|
|
851
|
+
outlined
|
|
852
|
+
pButton
|
|
853
|
+
[icon]="getDirectionIcon(sort.direction)"
|
|
854
|
+
type="button"
|
|
855
|
+
[ngClass]="'p-button-text p-button-sm' + getDirectionColor(sort.direction)"
|
|
856
|
+
pTooltip="{{ sort.direction === 'asc' ? 'تصاعدي' : 'تنازلي' }}"
|
|
857
|
+
tooltipPosition="top">
|
|
858
|
+
</button> -->
|
|
859
|
+
<!-- Move Up -->
|
|
860
|
+
<button
|
|
861
|
+
outlined
|
|
862
|
+
style=" width: var(--p-button-icon-only-width);"
|
|
863
|
+
*ngIf="!isFirst"
|
|
864
|
+
pButton
|
|
865
|
+
icon="pi pi-arrow-up"
|
|
866
|
+
type="button"
|
|
867
|
+
class=" p-button-sm p-button-success"
|
|
868
|
+
(click)="moveSort(i, i - 1)"
|
|
869
|
+
pTooltip="تحريك للأعلى"
|
|
870
|
+
tooltipPosition="top">
|
|
871
|
+
</button>
|
|
872
|
+
|
|
873
|
+
<!-- Move Down -->
|
|
874
|
+
<button
|
|
875
|
+
outlined
|
|
876
|
+
style=" width: var(--p-button-icon-only-width);"
|
|
877
|
+
*ngIf="!isLast"
|
|
878
|
+
pButton
|
|
879
|
+
icon="pi pi-arrow-down"
|
|
880
|
+
type="button"
|
|
881
|
+
class=" p-button-sm p-button-success"
|
|
882
|
+
(click)="moveSort(i, i + 1)"
|
|
883
|
+
pTooltip="تحريك للأسفل"
|
|
884
|
+
tooltipPosition="top">
|
|
885
|
+
</button>
|
|
886
|
+
|
|
887
|
+
<!-- Remove -->
|
|
888
|
+
<button
|
|
889
|
+
style=" width: var(--p-button-icon-only-width);"
|
|
890
|
+
pButton
|
|
891
|
+
icon="pi pi-times"
|
|
892
|
+
type="button"
|
|
893
|
+
class=" p-button-sm p-button-danger"
|
|
894
|
+
(click)="removeSort(i)"
|
|
895
|
+
pTooltip="حذف الترتيب"
|
|
896
|
+
tooltipPosition="top">
|
|
897
|
+
</button>
|
|
898
|
+
</div>
|
|
648
899
|
</div>
|
|
900
|
+
|
|
901
|
+
<!-- Field Info -->
|
|
902
|
+
<!-- <div *ngIf="sort.field" class="field-info mt-2 text-sm text-500">
|
|
903
|
+
<small>الحقل: {{ getFieldLabel(sort.field) }}</small>
|
|
904
|
+
</div> -->
|
|
649
905
|
</div>
|
|
650
906
|
</div>
|
|
651
907
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
class="
|
|
657
|
-
|
|
658
|
-
|
|
908
|
+
<!-- Empty State -->
|
|
909
|
+
<div *ngIf="sorts.length === 0" class="empty-state text-center p-4 border-round surface-section">
|
|
910
|
+
<i class="pi pi-sort-alt text-4xl text-500 mb-3"></i>
|
|
911
|
+
<p class="text-500 mb-3">لا توجد قواعد ترتيب محددة</p>
|
|
912
|
+
<p class="text-400 text-sm mb-3">سيتم استخدام الترتيب الافتراضي للبيانات</p>
|
|
913
|
+
</div>
|
|
914
|
+
|
|
915
|
+
<!-- Actions -->
|
|
916
|
+
<div class="flex gap-2">
|
|
917
|
+
<button
|
|
918
|
+
pButton
|
|
919
|
+
icon="pi pi-sort-alt"
|
|
920
|
+
label="إضافة ترتيب"
|
|
921
|
+
type="button"
|
|
922
|
+
class="p-button-outlined flex-1"
|
|
923
|
+
(click)="addSort()">
|
|
924
|
+
</button>
|
|
925
|
+
|
|
926
|
+
<button
|
|
927
|
+
*ngIf="sorts.length > 1"
|
|
928
|
+
pButton
|
|
929
|
+
icon="pi pi-random"
|
|
930
|
+
label="عكس الترتيب"
|
|
931
|
+
type="button"
|
|
932
|
+
class="p-button-outlined"
|
|
933
|
+
(click)="reverseSorts()"
|
|
934
|
+
pTooltip="عكس اتجاه جميع قواعد الترتيب"
|
|
935
|
+
tooltipPosition="top">
|
|
936
|
+
</button>
|
|
937
|
+
|
|
938
|
+
<button
|
|
939
|
+
*ngIf="sorts.length > 0"
|
|
940
|
+
pButton
|
|
941
|
+
icon="pi pi-trash"
|
|
942
|
+
type="button"
|
|
943
|
+
class="p-button-outlined p-button-danger"
|
|
944
|
+
(click)="clearAll()"
|
|
945
|
+
pTooltip="مسح جميع قواعد الترتيب"
|
|
946
|
+
tooltipPosition="top">
|
|
947
|
+
</button>
|
|
948
|
+
</div>
|
|
949
|
+
|
|
950
|
+
<!-- Summary -->
|
|
951
|
+
<div *ngIf="sorts.length > 0" class="sort-summary mt-3 p-2 border-round surface-ground">
|
|
952
|
+
<h5 class="mt-0 mb-2 text-sm">ملخص الترتيب:</h5>
|
|
953
|
+
<div class="text-sm text-500">
|
|
954
|
+
<span *ngFor="let sort of sorts; let i = index" class="sort-step">
|
|
955
|
+
<span class="font-semibold">{{ getFieldLabel(sort.field) }}</span>
|
|
956
|
+
<span class="direction-badge" [ngClass]="sort.direction === 'asc' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'">
|
|
957
|
+
{{ sort.direction === 'asc' ? '▲' : '▼' }}
|
|
958
|
+
</span>
|
|
959
|
+
<span *ngIf="!isLast"> ثم </span>
|
|
960
|
+
</span>
|
|
961
|
+
</div>
|
|
962
|
+
</div>
|
|
659
963
|
</div>
|
|
660
|
-
`
|
|
661
|
-
}]
|
|
964
|
+
` }]
|
|
662
965
|
}] });
|
|
663
966
|
|
|
664
|
-
//
|
|
665
|
-
class
|
|
666
|
-
|
|
967
|
+
// query-builder.component.ts
|
|
968
|
+
class QueryBuilderComponent extends FieldType {
|
|
969
|
+
queryBuilderService = inject(QueryBuilderService);
|
|
970
|
+
cdr = inject(ChangeDetectorRef);
|
|
667
971
|
translate = inject(TranslateService);
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
fields_ = [];
|
|
677
|
-
enableQueryBuilder = false;
|
|
678
|
-
odataConfig = {};
|
|
679
|
-
ngOnInit() {
|
|
680
|
-
this.buildQueryUIFields();
|
|
972
|
+
groups = [];
|
|
973
|
+
logicalOperators = [
|
|
974
|
+
{ label: this.translate.instant('and'), value: 'and' },
|
|
975
|
+
{ label: this.translate.instant('or'), value: 'or' }
|
|
976
|
+
];
|
|
977
|
+
onAndClick(group, event) {
|
|
978
|
+
group.logicalOperator = 'and';
|
|
979
|
+
this.updateValue();
|
|
681
980
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
981
|
+
onOrClick(group, event) {
|
|
982
|
+
group.logicalOperator = 'or';
|
|
983
|
+
this.updateValue();
|
|
984
|
+
}
|
|
985
|
+
ngOnInit() {
|
|
986
|
+
// Initialize with existing value or default group with conditions
|
|
987
|
+
if (this.formControl.value && (Array.isArray(this.formControl.value) && this.formControl.value.length > 0)) {
|
|
988
|
+
this.groups = this.formControl.value.map((group, groupIndex) => ({
|
|
989
|
+
logicalOperator: group.logicalOperator || 'and',
|
|
990
|
+
groupLogicalOperator: group.groupLogicalOperator || 'and',
|
|
991
|
+
conditions: this.initializeConditionsWithFieldConfigs(group.conditions || [], groupIndex)
|
|
992
|
+
}));
|
|
685
993
|
}
|
|
686
994
|
else {
|
|
687
|
-
|
|
995
|
+
// Start with default conditions for important fields
|
|
996
|
+
this.initializeDefaultConditions();
|
|
688
997
|
}
|
|
689
998
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
this.
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
999
|
+
getLogicalOperatorText(operator) {
|
|
1000
|
+
const operatorMap = {
|
|
1001
|
+
'and': this.translate.instant('and'),
|
|
1002
|
+
'or': this.translate.instant('or'),
|
|
1003
|
+
'not': this.translate.instant('not')
|
|
1004
|
+
};
|
|
1005
|
+
return operatorMap[operator] || operator;
|
|
1006
|
+
}
|
|
1007
|
+
initializeDefaultConditions() {
|
|
1008
|
+
// Get default fields using the service logic
|
|
1009
|
+
const defaultFields = this.getDefaultFields();
|
|
1010
|
+
const defaultConditions = [];
|
|
1011
|
+
for (let i = 0; i < defaultFields.length; i++) {
|
|
1012
|
+
const field = defaultFields[i];
|
|
1013
|
+
defaultConditions.push(this.createDefaultCondition(field, 0, i));
|
|
698
1014
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
1015
|
+
this.groups = [{
|
|
1016
|
+
logicalOperator: 'and',
|
|
1017
|
+
groupLogicalOperator: 'and',
|
|
1018
|
+
conditions: defaultConditions
|
|
1019
|
+
}];
|
|
1020
|
+
this.updateValue();
|
|
1021
|
+
}
|
|
1022
|
+
getDefaultFields() {
|
|
1023
|
+
// Get all filterable fields
|
|
1024
|
+
const filterableFields = this.props['fields'].filter((field) => field.filterable !== false && field.key && this.isSupportedFieldType(field.type));
|
|
1025
|
+
// If no filterable fields, return empty array
|
|
1026
|
+
if (filterableFields.length === 0) {
|
|
1027
|
+
return [];
|
|
703
1028
|
}
|
|
1029
|
+
const selectedFields = filterableFields;
|
|
1030
|
+
return selectedFields;
|
|
704
1031
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
this.odataSearch.emit({
|
|
710
|
-
filters: [],
|
|
711
|
-
orderBy: [],
|
|
712
|
-
groupBy: [],
|
|
713
|
-
expand: []
|
|
714
|
-
});
|
|
1032
|
+
isSupportedFieldType(fieldType) {
|
|
1033
|
+
// Only include field types that make sense for filtering
|
|
1034
|
+
const supportedTypes = ['input', 'number', 'datepicker', 'select', 'checkbox', 'generic-selector', 'switch', 'radio', 'textarea'];
|
|
1035
|
+
return supportedTypes.includes(fieldType);
|
|
715
1036
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1037
|
+
getFieldLabel(key) {
|
|
1038
|
+
const f = this.props['fields']?.find((x) => x.key === key);
|
|
1039
|
+
return f ? f.label : '';
|
|
1040
|
+
}
|
|
1041
|
+
createDefaultCondition(field, groupIndex, conditionIndex) {
|
|
1042
|
+
const defaultCondition = {
|
|
1043
|
+
field: field.key,
|
|
1044
|
+
operator: this.getDefaultOperator(field.type),
|
|
1045
|
+
value: this.getDefaultValue(field.type),
|
|
1046
|
+
valueFieldConfig: null,
|
|
1047
|
+
operatorsForField: this.getOperatorsForField(field),
|
|
723
1048
|
};
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1049
|
+
// Generate field config for this condition
|
|
1050
|
+
defaultCondition.valueFieldConfig = this.generateValueFieldConfig(defaultCondition, groupIndex, conditionIndex);
|
|
1051
|
+
return defaultCondition;
|
|
1052
|
+
}
|
|
1053
|
+
initializeConditionsWithFieldConfigs(conditions, groupIndex) {
|
|
1054
|
+
return conditions.map((condition, conditionIndex) => ({
|
|
1055
|
+
...condition,
|
|
1056
|
+
operatorsForField: this.getOperatorsForField(condition.field),
|
|
1057
|
+
valueFieldConfig: condition.field ?
|
|
1058
|
+
this.generateValueFieldConfig(condition, groupIndex, conditionIndex) :
|
|
1059
|
+
null
|
|
1060
|
+
}));
|
|
1061
|
+
}
|
|
1062
|
+
// Field type and operator methods that use QueryBuilderService logic
|
|
1063
|
+
getFieldType(fieldKey) {
|
|
1064
|
+
const field = this.props['fields'].find((f) => f.key === fieldKey);
|
|
1065
|
+
return field?.type || 'input';
|
|
1066
|
+
}
|
|
1067
|
+
getOperatorsForField(fieldKey) {
|
|
1068
|
+
if (!fieldKey)
|
|
1069
|
+
return [];
|
|
1070
|
+
const field = this.props['fields'].find((f) => f.key === fieldKey);
|
|
1071
|
+
// Use operators from the field configuration
|
|
1072
|
+
const operators = field?.operators || this.queryBuilderService['getOperatorsForType'](field?.type);
|
|
1073
|
+
return operators.map((op) => ({
|
|
1074
|
+
label: this.getOperatorLabel(op).label,
|
|
1075
|
+
icon: this.getOperatorLabel(op).icon,
|
|
1076
|
+
value: op
|
|
1077
|
+
}));
|
|
1078
|
+
}
|
|
1079
|
+
getOperatorLabel(operator) {
|
|
1080
|
+
const map = {
|
|
1081
|
+
eq: { label: this.translate.instant('eq'), icon: 'pi pi-equals' },
|
|
1082
|
+
ne: { label: this.translate.instant('ne'), icon: 'pi pi-hashtag' },
|
|
1083
|
+
contains: { label: this.translate.instant('contains'), icon: 'pi pi-search' },
|
|
1084
|
+
startswith: { label: this.translate.instant('startswith'), icon: 'pi pi-arrow-right' },
|
|
1085
|
+
endswith: { label: this.translate.instant('endswith'), icon: 'pi pi-arrow-left' },
|
|
1086
|
+
gt: { label: this.translate.instant('gt'), icon: 'pi pi-chevron-right' },
|
|
1087
|
+
ge: { label: this.translate.instant('ge'), icon: 'pi pi-angle-double-right' },
|
|
1088
|
+
lt: { label: this.translate.instant('lt'), icon: 'pi pi-chevron-left' },
|
|
1089
|
+
le: { label: this.translate.instant('le'), icon: 'pi pi-angle-double-left' },
|
|
1090
|
+
in: { label: this.translate.instant('in'), icon: 'pi pi-list' },
|
|
1091
|
+
notin: { label: this.translate.instant('notin'), icon: 'pi pi-ban' },
|
|
1092
|
+
};
|
|
1093
|
+
return map[operator] || { label: operator, icon: 'pi pi-question' };
|
|
1094
|
+
}
|
|
1095
|
+
getDefaultOperator(fieldType) {
|
|
1096
|
+
// Use service's field type config
|
|
1097
|
+
const fieldConfig = this.queryBuilderService['fieldTypeConfig'][fieldType];
|
|
1098
|
+
return fieldConfig ? fieldConfig.defaultOperator : 'contains';
|
|
1099
|
+
}
|
|
1100
|
+
getDefaultValue(fieldType) {
|
|
1101
|
+
// Use service's field type config
|
|
1102
|
+
const fieldConfig = this.queryBuilderService['fieldTypeConfig'][fieldType];
|
|
1103
|
+
return fieldConfig ? fieldConfig.defaultValue : '';
|
|
1104
|
+
}
|
|
1105
|
+
getFieldProps(fieldKey) {
|
|
1106
|
+
const field = this.props['fields'].find((f) => f.key === fieldKey);
|
|
1107
|
+
// Return props without label to avoid duplication
|
|
1108
|
+
const props = { ...field?.props };
|
|
1109
|
+
delete props.label;
|
|
1110
|
+
delete props.loadDefault;
|
|
1111
|
+
return props;
|
|
1112
|
+
}
|
|
1113
|
+
generateValueFieldConfig(condition, groupIndex, conditionIndex) {
|
|
1114
|
+
const fieldType = this.getFieldType(condition.field);
|
|
1115
|
+
const fieldProps = this.getFieldProps(condition.field);
|
|
1116
|
+
const fieldKey = this.getConditionValueKey(groupIndex, conditionIndex);
|
|
1117
|
+
return {
|
|
1118
|
+
key: fieldKey,
|
|
1119
|
+
type: fieldType,
|
|
1120
|
+
wrappers: [],
|
|
1121
|
+
props: {
|
|
1122
|
+
...fieldProps, // Include all other props from the original field
|
|
1123
|
+
placeholder: '',
|
|
1124
|
+
loadDefault: false,
|
|
1125
|
+
change: (field, event) => {
|
|
1126
|
+
this.updateValue();
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
hooks: {
|
|
1130
|
+
onInit: (field) => {
|
|
1131
|
+
if (field.formControl && condition.value !== undefined) {
|
|
1132
|
+
field.formControl.setValue(condition.value, { emitEvent: false });
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
},
|
|
1136
|
+
modelOptions: {
|
|
1137
|
+
updateOn: 'change'
|
|
740
1138
|
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
getConditionValueKey(groupIndex, conditionIndex) {
|
|
1142
|
+
return `condition_${groupIndex}_${conditionIndex}_value`;
|
|
1143
|
+
}
|
|
1144
|
+
// Group Management
|
|
1145
|
+
addGroup() {
|
|
1146
|
+
this.groups.push({
|
|
1147
|
+
logicalOperator: 'and',
|
|
1148
|
+
groupLogicalOperator: 'and',
|
|
1149
|
+
conditions: []
|
|
741
1150
|
});
|
|
742
|
-
|
|
1151
|
+
this.updateValue();
|
|
743
1152
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1153
|
+
removeGroup(groupIndex) {
|
|
1154
|
+
this.groups.splice(groupIndex, 1);
|
|
1155
|
+
this.updateValue();
|
|
1156
|
+
}
|
|
1157
|
+
// Condition Management
|
|
1158
|
+
addCondition(groupIndex = 0) {
|
|
1159
|
+
if (!this.groups[groupIndex]) {
|
|
1160
|
+
this.addGroup();
|
|
1161
|
+
}
|
|
1162
|
+
const newCondition = {
|
|
1163
|
+
field: '',
|
|
1164
|
+
operator: 'eq',
|
|
1165
|
+
value: '',
|
|
1166
|
+
valueFieldConfig: {}
|
|
1167
|
+
};
|
|
1168
|
+
const conditionIndex = this.groups[groupIndex].conditions.length;
|
|
1169
|
+
newCondition.valueFieldConfig = this.generateValueFieldConfig(newCondition, groupIndex, conditionIndex);
|
|
1170
|
+
this.groups[groupIndex].conditions.push(newCondition);
|
|
1171
|
+
this.updateValue();
|
|
1172
|
+
}
|
|
1173
|
+
onFieldChange(condition, groupIndex, conditionIndex) {
|
|
1174
|
+
condition.operator = this.getDefaultOperator(this.getFieldType(condition.field));
|
|
1175
|
+
condition.value = this.getDefaultValue(this.getFieldType(condition.field));
|
|
1176
|
+
// Generate and store the field config when field changes
|
|
1177
|
+
condition.valueFieldConfig = this.generateValueFieldConfig(condition, groupIndex, conditionIndex);
|
|
1178
|
+
condition.operatorsForField = this.getOperatorsForField(condition.field);
|
|
1179
|
+
this.updateValue();
|
|
1180
|
+
}
|
|
1181
|
+
addConditionToLastGroup() {
|
|
1182
|
+
const lastGroupIndex = this.groups.length - 1;
|
|
1183
|
+
if (lastGroupIndex >= 0) {
|
|
1184
|
+
this.addCondition(lastGroupIndex);
|
|
1185
|
+
}
|
|
1186
|
+
else {
|
|
1187
|
+
this.addGroup();
|
|
1188
|
+
this.addCondition(0);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
removeCondition(groupIndex, conditionIndex) {
|
|
1192
|
+
if (this.groups[groupIndex]) {
|
|
1193
|
+
this.groups[groupIndex].conditions.splice(conditionIndex, 1);
|
|
1194
|
+
// Remove empty groups
|
|
1195
|
+
if (this.groups[groupIndex].conditions.length === 0 && this.groups.length > 1) {
|
|
1196
|
+
this.removeGroup(groupIndex);
|
|
753
1197
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1198
|
+
this.updateValue();
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
// Value Management
|
|
1202
|
+
updateValue() {
|
|
1203
|
+
// Sync condition values from form model with safe access
|
|
1204
|
+
for (let g = 0; g < this.groups.length; g++) {
|
|
1205
|
+
const group = this.groups[g];
|
|
1206
|
+
for (let c = 0; c < group.conditions.length; c++) {
|
|
1207
|
+
const condition = group.conditions[c];
|
|
1208
|
+
const fieldKey = this.getConditionValueKey(g, c);
|
|
1209
|
+
const modelValue = this.model?.[fieldKey];
|
|
1210
|
+
if (modelValue !== undefined) {
|
|
1211
|
+
condition.value = modelValue;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
const filteredGroups = [];
|
|
1216
|
+
for (let g = 0; g < this.groups.length; g++) {
|
|
1217
|
+
const group = this.groups[g];
|
|
1218
|
+
const validConditions = [];
|
|
1219
|
+
for (let c = 0; c < group.conditions.length; c++) {
|
|
1220
|
+
const condition = group.conditions[c];
|
|
1221
|
+
if (condition.field &&
|
|
1222
|
+
condition.operator &&
|
|
1223
|
+
condition.value !== undefined &&
|
|
1224
|
+
condition.value !== null &&
|
|
1225
|
+
condition.value !== '') {
|
|
1226
|
+
validConditions.push(condition);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (validConditions.length > 0) {
|
|
1230
|
+
filteredGroups.push({ ...group, conditions: validConditions });
|
|
761
1231
|
}
|
|
762
1232
|
}
|
|
1233
|
+
this.formControl.setValue(filteredGroups);
|
|
1234
|
+
this.cdr.detectChanges();
|
|
763
1235
|
}
|
|
764
|
-
|
|
765
|
-
|
|
1236
|
+
clearAll() {
|
|
1237
|
+
this.groups = [];
|
|
1238
|
+
this.addGroup();
|
|
1239
|
+
}
|
|
1240
|
+
// Helper methods
|
|
1241
|
+
getQueryStructure() {
|
|
1242
|
+
return this.groups;
|
|
1243
|
+
}
|
|
1244
|
+
loadQueryStructure(queryStructure) {
|
|
1245
|
+
if (queryStructure && Array.isArray(queryStructure)) {
|
|
1246
|
+
this.groups = queryStructure.map((group, groupIndex) => ({
|
|
1247
|
+
logicalOperator: group.logicalOperator || 'and',
|
|
1248
|
+
groupLogicalOperator: group.groupLogicalOperator || 'and',
|
|
1249
|
+
conditions: this.initializeConditionsWithFieldConfigs(group.conditions || [], groupIndex)
|
|
1250
|
+
}));
|
|
1251
|
+
this.updateValue();
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
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" }] });
|
|
766
1256
|
}
|
|
767
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type:
|
|
1257
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: QueryBuilderComponent, decorators: [{
|
|
768
1258
|
type: Component,
|
|
769
|
-
args: [{ selector: '
|
|
1259
|
+
args: [{ selector: 'formly-query-builder', standalone: true, imports: [
|
|
770
1260
|
CommonModule,
|
|
1261
|
+
FormsModule,
|
|
1262
|
+
ReactiveFormsModule,
|
|
1263
|
+
SelectModule,
|
|
1264
|
+
InputTextModule,
|
|
1265
|
+
ButtonModule,
|
|
1266
|
+
DatePickerModule,
|
|
1267
|
+
CheckboxModule,
|
|
1268
|
+
RadioButtonModule,
|
|
771
1269
|
FormlyForm,
|
|
1270
|
+
TooltipModule,
|
|
1271
|
+
MenuModule,
|
|
772
1272
|
TranslateModule,
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
ReactiveFormsModule,
|
|
1273
|
+
InputGroupModule,
|
|
1274
|
+
InputGroupAddonModule,
|
|
776
1275
|
PopoverModule,
|
|
777
|
-
|
|
778
|
-
InputIconModule,
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1276
|
+
ListboxModule
|
|
1277
|
+
], 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"] }]
|
|
1278
|
+
}] });
|
|
1279
|
+
|
|
1280
|
+
// tab-type.component.ts
|
|
1281
|
+
class TabTypeComponent extends FieldType {
|
|
1282
|
+
activeTabValue = '0';
|
|
1283
|
+
ngOnInit() {
|
|
1284
|
+
if (this.props['activeIndex'] !== undefined) {
|
|
1285
|
+
this.activeTabValue = this.props['activeIndex'].toString();
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
resolveBadge(tab) {
|
|
1289
|
+
const badge = tab.props?.['badge'];
|
|
1290
|
+
if (typeof badge === 'function') {
|
|
1291
|
+
const result = badge(tab);
|
|
1292
|
+
return result == 0 ? null : result;
|
|
1293
|
+
}
|
|
1294
|
+
return badge ?? null;
|
|
1295
|
+
}
|
|
1296
|
+
onTabChange(newValue) {
|
|
1297
|
+
this.activeTabValue = newValue;
|
|
1298
|
+
if (this.props['onTabChange']) {
|
|
1299
|
+
this.props['onTabChange']({
|
|
1300
|
+
activeIndex: parseInt(newValue),
|
|
1301
|
+
tab: this.field.fieldGroup?.[parseInt(newValue)]
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: TabTypeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1306
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.8", type: TabTypeComponent, isStandalone: true, selector: "formly-tab-type", usesInheritance: true, ngImport: i0, template: `
|
|
1307
|
+
<p-tabs
|
|
1308
|
+
[value]="activeTabValue"
|
|
1309
|
+
[scrollable]="props['scrollable'] !== false"
|
|
1310
|
+
[lazy]="props['lazy'] !== false"
|
|
1311
|
+
(valueChange)="onTabChange($event)">
|
|
1312
|
+
|
|
1313
|
+
<p-tablist>
|
|
1314
|
+
<p-tab
|
|
1315
|
+
*ngFor="let tab of field.fieldGroup; let i = index"
|
|
1316
|
+
[value]="i.toString()"
|
|
1317
|
+
[disabled]="tab.props?.disabled">
|
|
1318
|
+
|
|
1319
|
+
@if(tab.props){
|
|
1320
|
+
<div class="flex align-items-center gap-2">
|
|
1321
|
+
<!-- Left Icon -->
|
|
1322
|
+
<i *ngIf="tab.props['leftIcon']" [class]="tab.props['leftIcon']" class="text-sm"></i>
|
|
1323
|
+
|
|
1324
|
+
<!-- Label -->
|
|
1325
|
+
<span>{{ (tab.props['label'] ?? '') | translate }}</span>
|
|
1326
|
+
|
|
1327
|
+
<!-- Badge -->
|
|
1328
|
+
|
|
1329
|
+
<p-badge
|
|
1330
|
+
*ngIf="resolveBadge(tab) !== null"
|
|
1331
|
+
[value]="resolveBadge(tab)"
|
|
1332
|
+
size="small">
|
|
1333
|
+
</p-badge>
|
|
1334
|
+
|
|
1335
|
+
<!-- Right Icon -->
|
|
1336
|
+
<i *ngIf="tab.props['rightIcon']" [class]="tab.props['rightIcon']" class="text-sm"></i>
|
|
1337
|
+
</div>
|
|
1338
|
+
}
|
|
1339
|
+
</p-tab>
|
|
1340
|
+
</p-tablist>
|
|
1341
|
+
|
|
1342
|
+
<p-tabpanels style="padding: 0;">
|
|
1343
|
+
<p-tabpanel
|
|
1344
|
+
*ngFor="let tab of field.fieldGroup; let i = index"
|
|
1345
|
+
[value]="i.toString()">
|
|
1346
|
+
|
|
1347
|
+
<div class="p-3">
|
|
1348
|
+
<formly-field [field]="tab"></formly-field>
|
|
1349
|
+
|
|
1350
|
+
<!-- Empty state -->
|
|
1351
|
+
<div
|
|
1352
|
+
*ngIf="!tab.fieldGroup || tab.fieldGroup.length === 0"
|
|
1353
|
+
class="empty-tab text-center p-4 text-500">
|
|
1354
|
+
<i class="pi pi-inbox text-2xl mb-2"></i>
|
|
1355
|
+
<p>لا توجد حقول في هذه التبويبة</p>
|
|
1356
|
+
</div>
|
|
1357
|
+
</div>
|
|
1358
|
+
</p-tabpanel>
|
|
1359
|
+
</p-tabpanels>
|
|
1360
|
+
</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$1.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2$1.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2$1.TabPanel, selector: "p-tabpanel", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: i2$1.TabList, selector: "p-tablist" }, { kind: "component", type: i2$1.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
|
+
}
|
|
1363
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: TabTypeComponent, decorators: [{
|
|
1364
|
+
type: Component,
|
|
1365
|
+
args: [{
|
|
1366
|
+
selector: 'formly-tab-type',
|
|
1367
|
+
template: `
|
|
1368
|
+
<p-tabs
|
|
1369
|
+
[value]="activeTabValue"
|
|
1370
|
+
[scrollable]="props['scrollable'] !== false"
|
|
1371
|
+
[lazy]="props['lazy'] !== false"
|
|
1372
|
+
(valueChange)="onTabChange($event)">
|
|
1373
|
+
|
|
1374
|
+
<p-tablist>
|
|
1375
|
+
<p-tab
|
|
1376
|
+
*ngFor="let tab of field.fieldGroup; let i = index"
|
|
1377
|
+
[value]="i.toString()"
|
|
1378
|
+
[disabled]="tab.props?.disabled">
|
|
1379
|
+
|
|
1380
|
+
@if(tab.props){
|
|
1381
|
+
<div class="flex align-items-center gap-2">
|
|
1382
|
+
<!-- Left Icon -->
|
|
1383
|
+
<i *ngIf="tab.props['leftIcon']" [class]="tab.props['leftIcon']" class="text-sm"></i>
|
|
1384
|
+
|
|
1385
|
+
<!-- Label -->
|
|
1386
|
+
<span>{{ (tab.props['label'] ?? '') | translate }}</span>
|
|
1387
|
+
|
|
1388
|
+
<!-- Badge -->
|
|
1389
|
+
|
|
1390
|
+
<p-badge
|
|
1391
|
+
*ngIf="resolveBadge(tab) !== null"
|
|
1392
|
+
[value]="resolveBadge(tab)"
|
|
1393
|
+
size="small">
|
|
1394
|
+
</p-badge>
|
|
1395
|
+
|
|
1396
|
+
<!-- Right Icon -->
|
|
1397
|
+
<i *ngIf="tab.props['rightIcon']" [class]="tab.props['rightIcon']" class="text-sm"></i>
|
|
1398
|
+
</div>
|
|
1399
|
+
}
|
|
1400
|
+
</p-tab>
|
|
1401
|
+
</p-tablist>
|
|
1402
|
+
|
|
1403
|
+
<p-tabpanels style="padding: 0;">
|
|
1404
|
+
<p-tabpanel
|
|
1405
|
+
*ngFor="let tab of field.fieldGroup; let i = index"
|
|
1406
|
+
[value]="i.toString()">
|
|
1407
|
+
|
|
1408
|
+
<div class="p-3">
|
|
1409
|
+
<formly-field [field]="tab"></formly-field>
|
|
1410
|
+
|
|
1411
|
+
<!-- Empty state -->
|
|
1412
|
+
<div
|
|
1413
|
+
*ngIf="!tab.fieldGroup || tab.fieldGroup.length === 0"
|
|
1414
|
+
class="empty-tab text-center p-4 text-500">
|
|
1415
|
+
<i class="pi pi-inbox text-2xl mb-2"></i>
|
|
1416
|
+
<p>لا توجد حقول في هذه التبويبة</p>
|
|
1417
|
+
</div>
|
|
1418
|
+
</div>
|
|
1419
|
+
</p-tabpanel>
|
|
1420
|
+
</p-tabpanels>
|
|
1421
|
+
</p-tabs>
|
|
1422
|
+
`,
|
|
1423
|
+
standalone: true,
|
|
1424
|
+
imports: [CommonModule, TabsModule, BadgeModule, FormlyField, TranslateModule]
|
|
1425
|
+
}]
|
|
1426
|
+
}] });
|
|
1427
|
+
|
|
1428
|
+
// group-type.component.ts
|
|
1429
|
+
class GroupTypeComponent extends FieldType {
|
|
1430
|
+
collapsed = this.props.collapsedByDefault ?? false;
|
|
1431
|
+
get showValidation() {
|
|
1432
|
+
return this.props.showValidation !== false &&
|
|
1433
|
+
this.formControl.invalid && (this.formControl.dirty || this.formControl.touched);
|
|
1434
|
+
}
|
|
1435
|
+
get validationIcon() {
|
|
1436
|
+
if (this.formControl.valid)
|
|
1437
|
+
return 'pi pi-check-circle text-green-500';
|
|
1438
|
+
if (this.formControl.invalid)
|
|
1439
|
+
return 'pi pi-exclamation-circle text-red-500';
|
|
1440
|
+
return 'pi pi-circle text-500';
|
|
1441
|
+
}
|
|
1442
|
+
get validationText() {
|
|
1443
|
+
return this.formControl.valid ? 'جميع الحقول صالحة' : 'هناك أخطاء في بعض الحقول';
|
|
1444
|
+
}
|
|
1445
|
+
get groupClass() {
|
|
1446
|
+
const v = this.props.variant || 'default';
|
|
1447
|
+
const map = {
|
|
1448
|
+
default: 'border-round border-1 border-200 bg-white mb-3',
|
|
1449
|
+
card: 'border-round surface-card shadow-sm mb-3',
|
|
1450
|
+
bordered: 'border-round border-1 border-300 surface-section mb-3',
|
|
1451
|
+
transparent: 'border-0 bg-transparent mb-3'
|
|
1452
|
+
};
|
|
1453
|
+
return map[v];
|
|
1454
|
+
}
|
|
1455
|
+
get gridClass() {
|
|
1456
|
+
const cols = this.props.columns || 1;
|
|
1457
|
+
const gap = this.props.gap || 'normal';
|
|
1458
|
+
const gaps = { none: 'gap-0', small: 'gap-2', normal: 'gap-3', large: 'gap-4' };
|
|
1459
|
+
return `grid grid-cols-1 ${cols > 1 ? 'md:grid-cols-' + cols : ''} ${gaps[gap]}`;
|
|
1460
|
+
}
|
|
1461
|
+
toggle() {
|
|
1462
|
+
this.collapsed = !this.collapsed;
|
|
1463
|
+
}
|
|
1464
|
+
isRequired() {
|
|
1465
|
+
return this.field.fieldGroup?.some(f => f.props?.required || f.validators?.['required']) ?? false;
|
|
1466
|
+
}
|
|
1467
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GroupTypeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1468
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.8", type: GroupTypeComponent, isStandalone: true, selector: "formly-group-type", usesInheritance: true, ngImport: i0, template: `
|
|
1469
|
+
<div [class]="groupClass">
|
|
1470
|
+
<div *ngIf="props.label || props.collapsible" class="flex justify-between items-center p-2"
|
|
1471
|
+
(click)="props.collapsible && toggle()">
|
|
1472
|
+
<div class="flex items-center gap-2">
|
|
1473
|
+
<i *ngIf="props.collapsible" [class]="collapsed ? 'pi pi-chevron-down' : 'pi pi-chevron-up'"></i>
|
|
1474
|
+
<label *ngIf="props.label" class="font-semibold">{{ props.label }}</label>
|
|
1475
|
+
<i *ngIf="isRequired()" class="pi pi-asterisk text-red-500 text-xs" pTooltip="حقل مطلوب"></i>
|
|
1476
|
+
</div>
|
|
1477
|
+
<div class="flex gap-2">
|
|
1478
|
+
<i *ngIf="props.description && !collapsed" class="pi pi-info-circle text-500"
|
|
1479
|
+
pTooltip="{{ props.description }}"></i>
|
|
1480
|
+
<i *ngIf="showValidation" [class]="validationIcon" [pTooltip]="validationText"></i>
|
|
1481
|
+
<button *ngFor="let a of props.actions || []" pButton type="button" class="p-button-text p-button-sm"
|
|
1482
|
+
[icon]="a.icon" [pTooltip]="a.tooltip" [ngClass]="a.styleClass"
|
|
1483
|
+
(click)="a.handler?.(field, form, this); $event.stopPropagation()"></button>
|
|
1484
|
+
</div>
|
|
1485
|
+
</div>
|
|
1486
|
+
|
|
1487
|
+
<div *ngIf="!collapsed" class="p-3 pt-0" [ngClass]="props.compact ? 'compact-layout' : ''">
|
|
1488
|
+
<p *ngIf="props.description && props.showDescription" class="text-500 text-sm mb-2">{{ props.description }}</p>
|
|
1489
|
+
|
|
1490
|
+
<div [ngClass]="gridClass">
|
|
1491
|
+
<ng-container *ngFor="let f of field.fieldGroup">
|
|
1492
|
+
<formly-field [field]="f"></formly-field>
|
|
1493
|
+
<small *ngIf="f.props?.description"
|
|
1494
|
+
class="block text-500 text-xs mt-1 ml-1">
|
|
1495
|
+
{{ f.props?.description }}
|
|
1496
|
+
</small>
|
|
1497
|
+
</ng-container>
|
|
1498
|
+
</div>
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
<div *ngIf="!field.fieldGroup?.length" class="text-center p-4 surface-section border-round">
|
|
1502
|
+
<i class="pi pi-inbox text-4xl text-500 mb-2"></i>
|
|
1503
|
+
<p class="text-500">لا توجد حقول</p>
|
|
1504
|
+
</div>
|
|
1505
|
+
</div>
|
|
1506
|
+
</div>
|
|
1507
|
+
`, isInline: true, 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: "component", type: FormlyField, selector: "formly-field", inputs: ["field"] }, { 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: 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: BadgeModule }] });
|
|
1508
|
+
}
|
|
1509
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GroupTypeComponent, decorators: [{
|
|
1510
|
+
type: Component,
|
|
1511
|
+
args: [{
|
|
1512
|
+
selector: 'formly-group-type',
|
|
1513
|
+
standalone: true,
|
|
1514
|
+
imports: [CommonModule, FormlyField, ButtonModule, TooltipModule, BadgeModule],
|
|
1515
|
+
template: `
|
|
1516
|
+
<div [class]="groupClass">
|
|
1517
|
+
<div *ngIf="props.label || props.collapsible" class="flex justify-between items-center p-2"
|
|
1518
|
+
(click)="props.collapsible && toggle()">
|
|
1519
|
+
<div class="flex items-center gap-2">
|
|
1520
|
+
<i *ngIf="props.collapsible" [class]="collapsed ? 'pi pi-chevron-down' : 'pi pi-chevron-up'"></i>
|
|
1521
|
+
<label *ngIf="props.label" class="font-semibold">{{ props.label }}</label>
|
|
1522
|
+
<i *ngIf="isRequired()" class="pi pi-asterisk text-red-500 text-xs" pTooltip="حقل مطلوب"></i>
|
|
1523
|
+
</div>
|
|
1524
|
+
<div class="flex gap-2">
|
|
1525
|
+
<i *ngIf="props.description && !collapsed" class="pi pi-info-circle text-500"
|
|
1526
|
+
pTooltip="{{ props.description }}"></i>
|
|
1527
|
+
<i *ngIf="showValidation" [class]="validationIcon" [pTooltip]="validationText"></i>
|
|
1528
|
+
<button *ngFor="let a of props.actions || []" pButton type="button" class="p-button-text p-button-sm"
|
|
1529
|
+
[icon]="a.icon" [pTooltip]="a.tooltip" [ngClass]="a.styleClass"
|
|
1530
|
+
(click)="a.handler?.(field, form, this); $event.stopPropagation()"></button>
|
|
1531
|
+
</div>
|
|
1532
|
+
</div>
|
|
1533
|
+
|
|
1534
|
+
<div *ngIf="!collapsed" class="p-3 pt-0" [ngClass]="props.compact ? 'compact-layout' : ''">
|
|
1535
|
+
<p *ngIf="props.description && props.showDescription" class="text-500 text-sm mb-2">{{ props.description }}</p>
|
|
1536
|
+
|
|
1537
|
+
<div [ngClass]="gridClass">
|
|
1538
|
+
<ng-container *ngFor="let f of field.fieldGroup">
|
|
1539
|
+
<formly-field [field]="f"></formly-field>
|
|
1540
|
+
<small *ngIf="f.props?.description"
|
|
1541
|
+
class="block text-500 text-xs mt-1 ml-1">
|
|
1542
|
+
{{ f.props?.description }}
|
|
1543
|
+
</small>
|
|
1544
|
+
</ng-container>
|
|
1545
|
+
</div>
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
<div *ngIf="!field.fieldGroup?.length" class="text-center p-4 surface-section border-round">
|
|
1549
|
+
<i class="pi pi-inbox text-4xl text-500 mb-2"></i>
|
|
1550
|
+
<p class="text-500">لا توجد حقول</p>
|
|
1551
|
+
</div>
|
|
1552
|
+
</div>
|
|
1553
|
+
</div>
|
|
1554
|
+
`
|
|
1555
|
+
}]
|
|
1556
|
+
}] });
|
|
1557
|
+
|
|
1558
|
+
const formlyConfig = provideFormlyConfig({
|
|
1559
|
+
types: [
|
|
1560
|
+
{
|
|
1561
|
+
name: 'query-builder',
|
|
1562
|
+
component: QueryBuilderComponent
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
name: 'sort-builder',
|
|
1566
|
+
component: SortBuilderComponent
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
name: 'group', component: GroupTypeComponent
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
name: 'tab-type',
|
|
1573
|
+
component: TabTypeComponent
|
|
1574
|
+
},
|
|
1575
|
+
],
|
|
1576
|
+
});
|
|
802
1577
|
|
|
803
1578
|
/**
|
|
804
1579
|
* Generated bundle index. Do not edit.
|
|
805
1580
|
*/
|
|
806
1581
|
|
|
807
|
-
export { GenericSearchAdvanced, GenericSearchAdvancedModule, QueryBuilderComponent, QueryBuilderService, SortBuilderComponent };
|
|
1582
|
+
export { GenericSearchAdvanced, GenericSearchAdvancedModule, GroupTypeComponent, QueryBuilderComponent, QueryBuilderService, SortBuilderComponent, TabTypeComponent, formlyConfig };
|
|
808
1583
|
//# sourceMappingURL=elite.framework-ng.ui.core-generic-search-advanced.mjs.map
|