@elite.framework/ng.ui.core 1.0.76 → 1.0.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,29 +1,45 @@
1
1
  import * as i0 from '@angular/core';
2
- import { NgModule, Injectable, Component, inject, EventEmitter, Input, Output, ViewChild } from '@angular/core';
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 i2 from '@angular/forms';
6
- import { FormsModule, FormGroup, ReactiveFormsModule } from '@angular/forms';
7
- import * as i6$1 from '@ngx-formly/core';
8
- import { FieldType, FormlyForm, FormlyModule } from '@ngx-formly/core';
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 i1$1 from 'primeng/button';
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 i4 from 'primeng/inputtext';
15
+ import * as i5 from 'primeng/inputtext';
19
16
  import { InputTextModule } from 'primeng/inputtext';
20
- import * as i5 from 'primeng/drawer';
17
+ import * as i6 from 'primeng/drawer';
21
18
  import { DrawerModule } from 'primeng/drawer';
22
- import * as i6 from 'primeng/checkbox';
23
- import { CheckboxModule } from 'primeng/checkbox';
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
- key: 'quickSearch',
46
- type: 'input',
83
+ type: 'tab-type',
84
+ // key: 'queryTabs',
47
85
  props: {
48
- label: 'بحث سريع',
49
- placeholder: 'ابحث في جميع الحقول...',
50
- icon: 'pi pi-search'
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: { label: 'التقسيم الصفحي' },
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
- key: 'skip',
87
- type: 'input',
88
- props: {
89
- type: 'number',
90
- label: 'تخطي السجلات',
91
- min: 0,
92
- description: 'عدد السجلات التي سيتم تخطيها'
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: this.mapFieldType(field.type),
116
- operators: this.getOperatorsForType(field.type)
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': 'string',
255
+ 'input': 'input',
160
256
  'number': 'number',
161
- 'datepicker': 'date',
162
- 'select': 'string',
163
- 'checkbox': 'boolean',
164
- 'radio': 'string',
165
- 'textarea': 'string'
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'] || 'string';
265
+ return typeMap[type || 'input'] || 'input';
168
266
  }
169
267
  getOperatorsForType(type) {
170
- const operatorsMap = {
171
- 'string': ['eq', 'ne', 'contains', 'startswith', 'endswith'],
172
- 'number': ['eq', 'ne', 'gt', 'ge', 'lt', 'le'],
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.forEach(field => {
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
- field: field.key,
191
- operator: 'contains',
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.forEach((filter) => {
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.forEach((sort) => {
213
- if (sort.field && sort.direction) {
214
- params.orderBy.push({
215
- field: sort.field,
216
- direction: sort.direction
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
- const queryParts = [];
234
- // Filters
235
- if (params.filters.length > 0) {
236
- const filterGroups = {};
237
- params.filters.forEach(filter => {
238
- const logicalOp = filter.logicalOperator || 'and';
239
- if (!filterGroups[logicalOp]) {
240
- filterGroups[logicalOp] = [];
241
- }
242
- filterGroups[logicalOp].push(`${filter.field} ${filter.operator} ${this.formatODataValue(filter.value)}`);
243
- });
244
- let filterString = '';
245
- Object.keys(filterGroups).forEach((op, index) => {
246
- if (index > 0)
247
- filterString += ' and ';
248
- if (filterGroups[op].length > 1) {
249
- filterString += `(${filterGroups[op].join(` ${op} `)})`;
250
- }
251
- else {
252
- filterString += filterGroups[op][0];
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
- // Order by
258
- if (params.orderBy.length > 0) {
259
- const orderByString = params.orderBy.map(order => `${order.field} ${order.direction}`).join(',');
260
- queryParts.push(`$orderby=${encodeURIComponent(orderByString)}`);
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
- // Expand
263
- if (params.expand && params.expand.length > 0) {
264
- queryParts.push(`$expand=${encodeURIComponent(params.expand.join(','))}`);
358
+ // Parse pagination
359
+ if (params.top !== undefined) {
360
+ model.pagination.top = params.top;
265
361
  }
266
- // Top
267
- if (params.top) {
268
- queryParts.push(`$top=${params.top}`);
362
+ if (params.skip !== undefined) {
363
+ model.pagination.skip = params.skip;
269
364
  }
270
- // Skip
271
- if (params.skip) {
272
- queryParts.push(`$skip=${params.skip}`);
365
+ // Parse expand
366
+ if (params.expand) {
367
+ model.expand = params.expand;
273
368
  }
274
- return queryParts.length > 0 ? '?' + queryParts.join('&') : '';
369
+ return model;
275
370
  }
276
- formatODataValue(value) {
277
- if (typeof value === 'string') {
278
- return `'${value.replace(/'/g, "''")}'`;
279
- }
280
- else if (value instanceof Date) {
281
- return `'${value.toISOString()}'`;
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
- // query-builder.component.ts
299
- class QueryBuilderComponent extends FieldType {
300
- conditions = [];
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
- if (this.formControl.value && Array.isArray(this.formControl.value)) {
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
- onFieldChange(condition) {
315
- condition.operator = 'eq';
316
- condition.value = '';
317
- this.updateValue();
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
- updateValue() {
320
- this.formControl.setValue([...this.conditions]);
321
- }
322
- getOperatorsForField(fieldKey) {
323
- if (!fieldKey)
324
- return [];
325
- const field = this.props['fields'].find((f) => f.key === fieldKey);
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
- getOperatorLabel(operator) {
333
- const labels = {
334
- 'eq': 'يساوي',
335
- 'ne': 'لا يساوي',
336
- 'contains': 'يحتوي على',
337
- 'startswith': 'يبدأ بـ',
338
- 'endswith': 'ينتهي بـ',
339
- 'gt': 'أكبر من',
340
- 'ge': 'أكبر أو يساوي',
341
- 'lt': 'أصغر من',
342
- 'le': 'أصغر أو يساوي'
343
- };
344
- return labels[operator] || operator;
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
- getFieldType(fieldKey) {
347
- const field = this.props['fields'].find((f) => f.key === fieldKey);
348
- return field?.type || 'string';
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
- getInputType(fieldKey) {
351
- const type = this.getFieldType(fieldKey);
352
- return type === 'number' ? 'number' : 'text';
459
+ // Toggle drawer visibility
460
+ toggleDrawer() {
461
+ this.drawerVisible = !this.drawerVisible;
353
462
  }
354
- getValuePlaceholder(fieldKey) {
355
- return 'القيمة';
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: QueryBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
358
- 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: `
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: QueryBuilderComponent, decorators: [{
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
- SelectModule,
480
+ InputIconModule,
481
+ IconFieldModule,
445
482
  InputTextModule,
446
- ButtonModule,
447
- DatePickerModule,
448
- CheckboxModule
449
- ],
450
- template: `
451
- <div class="query-builder p-3 border-round border-1 surface-border">
452
- <h4 class="mt-0">{{ props.label }}</h4>
453
-
454
- <div *ngFor="let condition of conditions; let i = index" class="condition-row mb-2 p-2 border-round surface-card">
455
- <div class="grid grid-nogutter align-items-center gap-2">
456
- <!-- Field Selector -->
457
- <div class="col">
458
- <p-select
459
- [options]="props['fields']"
460
- optionLabel="label"
461
- optionValue="key"
462
- [(ngModel)]="condition.field"
463
- (onChange)="onFieldChange(condition)"
464
- placeholder="اختر الحقل"
465
- [showClear]="true"
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 = [...this.formControl.value];
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
- this.formControl.setValue([...this.sorts]);
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
- <h4 class="mt-0">{{ props.label }}</h4>
557
-
558
- <div *ngFor="let sort of sorts; let i = index" class="sort-row mb-2 p-2 border-round surface-card">
559
- <div class="grid grid-nogutter align-items-center gap-2">
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
- <!-- Direction Selector -->
574
- <div class="col">
575
- <p-select
576
- [options]="directionOptions"
577
- [(ngModel)]="sort.direction"
578
- placeholder="الاتجاه"
579
- styleClass="w-full">
580
- </p-select>
581
- </div>
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
- <!-- Remove Button -->
584
- <div class="col-fixed">
585
- <button
586
- pButton
587
- icon="pi pi-times"
588
- class="p-button-danger p-button-text"
589
- (click)="removeSort(i)">
590
- </button>
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
- <button
596
- pButton
597
- icon="pi pi-sort-alt"
598
- label="إضافة ترتيب"
599
- class="p-button-outlined w-full"
600
- (click)="addSort()">
601
- </button>
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: 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: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }] });
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
- selector: 'formly-sort-builder',
609
- standalone: true,
610
- imports: [CommonModule, FormsModule, SelectModule, ButtonModule],
611
- template: `
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
- <h4 class="mt-0">{{ props.label }}</h4>
614
-
615
- <div *ngFor="let sort of sorts; let i = index" class="sort-row mb-2 p-2 border-round surface-card">
616
- <div class="grid grid-nogutter align-items-center gap-2">
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
- <!-- Direction Selector -->
631
- <div class="col">
632
- <p-select
633
- [options]="directionOptions"
634
- [(ngModel)]="sort.direction"
635
- placeholder="الاتجاه"
636
- styleClass="w-full">
637
- </p-select>
638
- </div>
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
- <!-- Remove Button -->
641
- <div class="col-fixed">
642
- <button
643
- pButton
644
- icon="pi pi-times"
645
- class="p-button-danger p-button-text"
646
- (click)="removeSort(i)">
647
- </button>
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
- <button
653
- pButton
654
- icon="pi pi-sort-alt"
655
- label="إضافة ترتيب"
656
- class="p-button-outlined w-full"
657
- (click)="addSort()">
658
- </button>
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
- // generic-search.component.ts
665
- class GenericSearchAdvanced {
666
- overlay;
967
+ // query-builder.component.ts
968
+ class QueryBuilderComponent extends FieldType {
969
+ queryBuilderService = inject(QueryBuilderService);
970
+ cdr = inject(ChangeDetectorRef);
667
971
  translate = inject(TranslateService);
668
- queryBuilder = inject(QueryBuilderService);
669
- drawerVisible = false;
670
- search = new EventEmitter();
671
- odataSearch = new EventEmitter();
672
- form = new FormGroup({});
673
- model = {};
674
- options = {};
675
- fields = [];
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
- buildQueryUIFields() {
683
- if (this.enableQueryBuilder) {
684
- this.fields_ = this.queryBuilder.buildQueryUIFields(this.fields);
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
- this.fields_ = this.fields;
995
+ // Start with default conditions for important fields
996
+ this.initializeDefaultConditions();
688
997
  }
689
998
  }
690
- onSubmit() {
691
- this.search.emit(this.model);
692
- if (this.enableQueryBuilder) {
693
- const odataParams = this.queryBuilder.buildODataFromQueryModel(this.model, this.fields);
694
- this.odataSearch.emit(odataParams);
695
- // Generate query string
696
- const queryString = this.queryBuilder.toODataQueryString(odataParams);
697
- console.log('OData Query String:', queryString);
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
- else {
700
- // Use existing logic for non-query-builder mode
701
- const odataParams = this.buildODataQueryParameters(this.model);
702
- this.odataSearch.emit(odataParams);
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
- onReset() {
706
- this.model = {};
707
- this.form.reset();
708
- this.search.emit(this.model);
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
- // Existing OData building methods for backward compatibility
717
- buildODataQueryParameters(model) {
718
- const params = {
719
- filters: [],
720
- orderBy: [],
721
- groupBy: [],
722
- expand: this.odataConfig.expandFields || []
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
- Object.keys(model).forEach(key => {
725
- const value = model[key];
726
- if (value === null || value === undefined || value === '' ||
727
- (Array.isArray(value) && value.length === 0)) {
728
- return;
729
- }
730
- if (typeof value === 'object' && value !== null) {
731
- this.processComplexField(key, value, params);
732
- }
733
- else {
734
- params.filters.push({
735
- field: key,
736
- operator: 'eq',
737
- value: value,
738
- logicalOperator: 'and'
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
- return params;
1151
+ this.updateValue();
743
1152
  }
744
- processComplexField(key, value, params) {
745
- if (value.from || value.to) {
746
- if (value.from) {
747
- params.filters.push({
748
- field: key,
749
- operator: 'ge',
750
- value: value.from,
751
- logicalOperator: 'and'
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
- if (value.to) {
755
- params.filters.push({
756
- field: key,
757
- operator: 'le',
758
- value: value.to,
759
- logicalOperator: 'and'
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
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: GenericSearchAdvanced, deps: [], target: i0.ɵɵFactoryTarget.Component });
765
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.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", odataSearch: "odataSearch" }, viewQueries: [{ propertyName: "overlay", first: true, predicate: ["overlay"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-wrap items-center gap-3 w-full\">\r\n <!-- Search Input with Icon -->\r\n <p-iconfield iconPosition=\"left\">\r\n <input\r\n pInputText\r\n type=\"text\"\r\n (keyup.enter)=\"onSubmit()\"\r\n [(ngModel)]=\"model['filter']\"\r\n placeholder=\"{{ 'SEARCH' | translate }}\"\r\n />\r\n </p-iconfield>\r\n\r\n <!-- Filter Button -->\r\n <p-button\r\n icon=\"pi pi-filter\"\r\n outlined\r\n severity=\"secondary\"\r\n (onClick)=\"drawerVisible = true\"\r\n ></p-button>\r\n</div>\r\n\r\n<!-- Drawer for Advanced Multi-field Search -->\r\n<p-drawer\r\n [(visible)]=\"drawerVisible\"\r\n position=\"right\"\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 <!-- Action Buttons -->\r\n <div class=\"flex justify-end mt-2 space-x-2 flex-none\">\r\n <button type=\"submit\" pButton size=\"small\" label=\"{{ 'SEARCH' | translate }}\"></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 </form>\r\n</p-drawer>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: ToolbarModule }, { 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: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.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: "component", type: i3$1.IconField, selector: "p-iconfield, p-iconField, p-icon-field", inputs: ["iconPosition", "styleClass"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i5.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: FormlyModule }, { kind: "component", type: i6$1.LegacyFormlyForm, selector: "formly-form" }, { kind: "pipe", type: i7.TranslatePipe, name: "translate" }] });
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: GenericSearchAdvanced, decorators: [{
1257
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.8", ngImport: i0, type: QueryBuilderComponent, decorators: [{
768
1258
  type: Component,
769
- args: [{ selector: 'lib-generic-search-advanced', standalone: true, imports: [
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
- ToolbarModule,
774
- ButtonModule,
775
- ReactiveFormsModule,
1273
+ InputGroupModule,
1274
+ InputGroupAddonModule,
776
1275
  PopoverModule,
777
- FormsModule,
778
- InputIconModule,
779
- IconFieldModule,
780
- InputTextModule,
781
- DrawerModule,
782
- FormlyModule,
783
- QueryBuilderComponent,
784
- SortBuilderComponent
785
- ], template: "<div class=\"flex flex-wrap items-center gap-3 w-full\">\r\n <!-- Search Input with Icon -->\r\n <p-iconfield iconPosition=\"left\">\r\n <input\r\n pInputText\r\n type=\"text\"\r\n (keyup.enter)=\"onSubmit()\"\r\n [(ngModel)]=\"model['filter']\"\r\n placeholder=\"{{ 'SEARCH' | translate }}\"\r\n />\r\n </p-iconfield>\r\n\r\n <!-- Filter Button -->\r\n <p-button\r\n icon=\"pi pi-filter\"\r\n outlined\r\n severity=\"secondary\"\r\n (onClick)=\"drawerVisible = true\"\r\n ></p-button>\r\n</div>\r\n\r\n<!-- Drawer for Advanced Multi-field Search -->\r\n<p-drawer\r\n [(visible)]=\"drawerVisible\"\r\n position=\"right\"\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 <!-- Action Buttons -->\r\n <div class=\"flex justify-end mt-2 space-x-2 flex-none\">\r\n <button type=\"submit\" pButton size=\"small\" label=\"{{ 'SEARCH' | translate }}\"></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 </form>\r\n</p-drawer>\r\n" }]
786
- }], propDecorators: { overlay: [{
787
- type: ViewChild,
788
- args: ['overlay']
789
- }], search: [{
790
- type: Output
791
- }], odataSearch: [{
792
- type: Output
793
- }], model: [{
794
- type: Input
795
- }], fields: [{
796
- type: Input
797
- }], enableQueryBuilder: [{
798
- type: Input
799
- }], odataConfig: [{
800
- type: Input
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