@es.framework/ng.ui.core 2.0.58

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.
Files changed (246) hide show
  1. package/README.md +3 -0
  2. package/attachments/README.md +3 -0
  3. package/base-crud/README.md +3 -0
  4. package/breadcrumb/README.md +3 -0
  5. package/checkbox/README.md +3 -0
  6. package/collapsible/README.md +3 -0
  7. package/color-picker/README.md +3 -0
  8. package/column-settings-popover/README.md +3 -0
  9. package/custom-switch/README.md +3 -0
  10. package/datepicker/README.md +3 -0
  11. package/deactivation-reason/README.md +3 -0
  12. package/excel-import/README.md +3 -0
  13. package/fesm2022/es.framework-ng.ui.core-attachments.mjs +217 -0
  14. package/fesm2022/es.framework-ng.ui.core-attachments.mjs.map +1 -0
  15. package/fesm2022/es.framework-ng.ui.core-base-crud.mjs +370 -0
  16. package/fesm2022/es.framework-ng.ui.core-base-crud.mjs.map +1 -0
  17. package/fesm2022/es.framework-ng.ui.core-breadcrumb.mjs +145 -0
  18. package/fesm2022/es.framework-ng.ui.core-breadcrumb.mjs.map +1 -0
  19. package/fesm2022/es.framework-ng.ui.core-checkbox.mjs +131 -0
  20. package/fesm2022/es.framework-ng.ui.core-checkbox.mjs.map +1 -0
  21. package/fesm2022/es.framework-ng.ui.core-collapsible.mjs +90 -0
  22. package/fesm2022/es.framework-ng.ui.core-collapsible.mjs.map +1 -0
  23. package/fesm2022/es.framework-ng.ui.core-color-picker.mjs +87 -0
  24. package/fesm2022/es.framework-ng.ui.core-color-picker.mjs.map +1 -0
  25. package/fesm2022/es.framework-ng.ui.core-column-settings-popover.mjs +183 -0
  26. package/fesm2022/es.framework-ng.ui.core-column-settings-popover.mjs.map +1 -0
  27. package/fesm2022/es.framework-ng.ui.core-custom-switch.mjs +119 -0
  28. package/fesm2022/es.framework-ng.ui.core-custom-switch.mjs.map +1 -0
  29. package/fesm2022/es.framework-ng.ui.core-datepicker.mjs +519 -0
  30. package/fesm2022/es.framework-ng.ui.core-datepicker.mjs.map +1 -0
  31. package/fesm2022/es.framework-ng.ui.core-deactivation-reason.mjs +144 -0
  32. package/fesm2022/es.framework-ng.ui.core-deactivation-reason.mjs.map +1 -0
  33. package/fesm2022/es.framework-ng.ui.core-excel-import.mjs +566 -0
  34. package/fesm2022/es.framework-ng.ui.core-excel-import.mjs.map +1 -0
  35. package/fesm2022/es.framework-ng.ui.core-filter-templates.mjs +386 -0
  36. package/fesm2022/es.framework-ng.ui.core-filter-templates.mjs.map +1 -0
  37. package/fesm2022/es.framework-ng.ui.core-form-button.mjs +62 -0
  38. package/fesm2022/es.framework-ng.ui.core-form-button.mjs.map +1 -0
  39. package/fesm2022/es.framework-ng.ui.core-form-field.mjs +155 -0
  40. package/fesm2022/es.framework-ng.ui.core-form-field.mjs.map +1 -0
  41. package/fesm2022/es.framework-ng.ui.core-form-template.mjs +37 -0
  42. package/fesm2022/es.framework-ng.ui.core-form-template.mjs.map +1 -0
  43. package/fesm2022/es.framework-ng.ui.core-formly-avatar-image.mjs +132 -0
  44. package/fesm2022/es.framework-ng.ui.core-formly-avatar-image.mjs.map +1 -0
  45. package/fesm2022/es.framework-ng.ui.core-formly-avatar-label.mjs +89 -0
  46. package/fesm2022/es.framework-ng.ui.core-formly-avatar-label.mjs.map +1 -0
  47. package/fesm2022/es.framework-ng.ui.core-formly-button-selector.mjs +195 -0
  48. package/fesm2022/es.framework-ng.ui.core-formly-button-selector.mjs.map +1 -0
  49. package/fesm2022/es.framework-ng.ui.core-formly-button.mjs +73 -0
  50. package/fesm2022/es.framework-ng.ui.core-formly-button.mjs.map +1 -0
  51. package/fesm2022/es.framework-ng.ui.core-formly-presets.mjs +201 -0
  52. package/fesm2022/es.framework-ng.ui.core-formly-presets.mjs.map +1 -0
  53. package/fesm2022/es.framework-ng.ui.core-formly-prime-icon-picker.mjs +542 -0
  54. package/fesm2022/es.framework-ng.ui.core-formly-prime-icon-picker.mjs.map +1 -0
  55. package/fesm2022/es.framework-ng.ui.core-formly-split-button.mjs +62 -0
  56. package/fesm2022/es.framework-ng.ui.core-formly-split-button.mjs.map +1 -0
  57. package/fesm2022/es.framework-ng.ui.core-formly-ui-all.mjs +422 -0
  58. package/fesm2022/es.framework-ng.ui.core-formly-ui-all.mjs.map +1 -0
  59. package/fesm2022/es.framework-ng.ui.core-formly-ui.mjs +48 -0
  60. package/fesm2022/es.framework-ng.ui.core-formly-ui.mjs.map +1 -0
  61. package/fesm2022/es.framework-ng.ui.core-formly-username-with-domain.mjs +85 -0
  62. package/fesm2022/es.framework-ng.ui.core-formly-username-with-domain.mjs.map +1 -0
  63. package/fesm2022/es.framework-ng.ui.core-generic-assets.mjs +22 -0
  64. package/fesm2022/es.framework-ng.ui.core-generic-assets.mjs.map +1 -0
  65. package/fesm2022/es.framework-ng.ui.core-generic-autocomplete.mjs +173 -0
  66. package/fesm2022/es.framework-ng.ui.core-generic-autocomplete.mjs.map +1 -0
  67. package/fesm2022/es.framework-ng.ui.core-generic-button.mjs +150 -0
  68. package/fesm2022/es.framework-ng.ui.core-generic-button.mjs.map +1 -0
  69. package/fesm2022/es.framework-ng.ui.core-generic-card.mjs +218 -0
  70. package/fesm2022/es.framework-ng.ui.core-generic-card.mjs.map +1 -0
  71. package/fesm2022/es.framework-ng.ui.core-generic-crud-table.mjs +929 -0
  72. package/fesm2022/es.framework-ng.ui.core-generic-crud-table.mjs.map +1 -0
  73. package/fesm2022/es.framework-ng.ui.core-generic-dialog.mjs +321 -0
  74. package/fesm2022/es.framework-ng.ui.core-generic-dialog.mjs.map +1 -0
  75. package/fesm2022/es.framework-ng.ui.core-generic-errormessage.mjs +35 -0
  76. package/fesm2022/es.framework-ng.ui.core-generic-errormessage.mjs.map +1 -0
  77. package/fesm2022/es.framework-ng.ui.core-generic-formly-fields.mjs +63 -0
  78. package/fesm2022/es.framework-ng.ui.core-generic-formly-fields.mjs.map +1 -0
  79. package/fesm2022/es.framework-ng.ui.core-generic-loadingspinner.mjs +34 -0
  80. package/fesm2022/es.framework-ng.ui.core-generic-loadingspinner.mjs.map +1 -0
  81. package/fesm2022/es.framework-ng.ui.core-generic-report-tabs.mjs +196 -0
  82. package/fesm2022/es.framework-ng.ui.core-generic-report-tabs.mjs.map +1 -0
  83. package/fesm2022/es.framework-ng.ui.core-generic-report.mjs +556 -0
  84. package/fesm2022/es.framework-ng.ui.core-generic-report.mjs.map +1 -0
  85. package/fesm2022/es.framework-ng.ui.core-generic-search-advanced.mjs +2995 -0
  86. package/fesm2022/es.framework-ng.ui.core-generic-search-advanced.mjs.map +1 -0
  87. package/fesm2022/es.framework-ng.ui.core-generic-search.mjs +103 -0
  88. package/fesm2022/es.framework-ng.ui.core-generic-search.mjs.map +1 -0
  89. package/fesm2022/es.framework-ng.ui.core-generic-selector.mjs +684 -0
  90. package/fesm2022/es.framework-ng.ui.core-generic-selector.mjs.map +1 -0
  91. package/fesm2022/es.framework-ng.ui.core-generic-table.mjs +388 -0
  92. package/fesm2022/es.framework-ng.ui.core-generic-table.mjs.map +1 -0
  93. package/fesm2022/es.framework-ng.ui.core-generic-view.mjs +465 -0
  94. package/fesm2022/es.framework-ng.ui.core-generic-view.mjs.map +1 -0
  95. package/fesm2022/es.framework-ng.ui.core-header-wrapper.mjs +31 -0
  96. package/fesm2022/es.framework-ng.ui.core-header-wrapper.mjs.map +1 -0
  97. package/fesm2022/es.framework-ng.ui.core-icon-picker.mjs +205 -0
  98. package/fesm2022/es.framework-ng.ui.core-icon-picker.mjs.map +1 -0
  99. package/fesm2022/es.framework-ng.ui.core-input-switch.mjs +102 -0
  100. package/fesm2022/es.framework-ng.ui.core-input-switch.mjs.map +1 -0
  101. package/fesm2022/es.framework-ng.ui.core-input-with-icon.mjs +67 -0
  102. package/fesm2022/es.framework-ng.ui.core-input-with-icon.mjs.map +1 -0
  103. package/fesm2022/es.framework-ng.ui.core-input.mjs +250 -0
  104. package/fesm2022/es.framework-ng.ui.core-input.mjs.map +1 -0
  105. package/fesm2022/es.framework-ng.ui.core-label-type.mjs +184 -0
  106. package/fesm2022/es.framework-ng.ui.core-label-type.mjs.map +1 -0
  107. package/fesm2022/es.framework-ng.ui.core-loading-skeletons.mjs +66 -0
  108. package/fesm2022/es.framework-ng.ui.core-loading-skeletons.mjs.map +1 -0
  109. package/fesm2022/es.framework-ng.ui.core-odata-query-builder.mjs +307 -0
  110. package/fesm2022/es.framework-ng.ui.core-odata-query-builder.mjs.map +1 -0
  111. package/fesm2022/es.framework-ng.ui.core-query-type.mjs +162 -0
  112. package/fesm2022/es.framework-ng.ui.core-query-type.mjs.map +1 -0
  113. package/fesm2022/es.framework-ng.ui.core-radio.mjs +120 -0
  114. package/fesm2022/es.framework-ng.ui.core-radio.mjs.map +1 -0
  115. package/fesm2022/es.framework-ng.ui.core-repeat.mjs +290 -0
  116. package/fesm2022/es.framework-ng.ui.core-repeat.mjs.map +1 -0
  117. package/fesm2022/es.framework-ng.ui.core-select.mjs +179 -0
  118. package/fesm2022/es.framework-ng.ui.core-select.mjs.map +1 -0
  119. package/fesm2022/es.framework-ng.ui.core-sidebar-cards.mjs +52 -0
  120. package/fesm2022/es.framework-ng.ui.core-sidebar-cards.mjs.map +1 -0
  121. package/fesm2022/es.framework-ng.ui.core-sidebar-toggles.mjs +53 -0
  122. package/fesm2022/es.framework-ng.ui.core-sidebar-toggles.mjs.map +1 -0
  123. package/fesm2022/es.framework-ng.ui.core-tabs.mjs +57 -0
  124. package/fesm2022/es.framework-ng.ui.core-tabs.mjs.map +1 -0
  125. package/fesm2022/es.framework-ng.ui.core-tag-type.mjs +209 -0
  126. package/fesm2022/es.framework-ng.ui.core-tag-type.mjs.map +1 -0
  127. package/fesm2022/es.framework-ng.ui.core-text-editor.mjs +58 -0
  128. package/fesm2022/es.framework-ng.ui.core-text-editor.mjs.map +1 -0
  129. package/fesm2022/es.framework-ng.ui.core-textarea.mjs +93 -0
  130. package/fesm2022/es.framework-ng.ui.core-textarea.mjs.map +1 -0
  131. package/fesm2022/es.framework-ng.ui.core-wrappers.mjs +245 -0
  132. package/fesm2022/es.framework-ng.ui.core-wrappers.mjs.map +1 -0
  133. package/fesm2022/es.framework-ng.ui.core.mjs +20 -0
  134. package/fesm2022/es.framework-ng.ui.core.mjs.map +1 -0
  135. package/filter-templates/README.md +3 -0
  136. package/form-button/README.md +3 -0
  137. package/form-field/README.md +3 -0
  138. package/form-template/README.md +3 -0
  139. package/formly-avatar-image/README.md +3 -0
  140. package/formly-avatar-label/README.md +3 -0
  141. package/formly-button/README.md +3 -0
  142. package/formly-button-selector/README.md +3 -0
  143. package/formly-presets/README.md +3 -0
  144. package/formly-prime-icon-picker/README.md +3 -0
  145. package/formly-split-button/README.md +3 -0
  146. package/formly-ui/README.md +3 -0
  147. package/formly-ui-all/README.md +3 -0
  148. package/formly-username-with-domain/README.md +3 -0
  149. package/generic-assets/README.md +3 -0
  150. package/generic-autocomplete/README.md +3 -0
  151. package/generic-button/README.md +3 -0
  152. package/generic-card/README.md +3 -0
  153. package/generic-crud-table/README.md +3 -0
  154. package/generic-dialog/README.md +3 -0
  155. package/generic-errormessage/README.md +3 -0
  156. package/generic-formly-fields/README.md +3 -0
  157. package/generic-loadingspinner/README.md +3 -0
  158. package/generic-report/README.md +3 -0
  159. package/generic-report-tabs/README.md +0 -0
  160. package/generic-search/README.md +3 -0
  161. package/generic-search-advanced/README.md +3 -0
  162. package/generic-selector/README.md +3 -0
  163. package/generic-table/README.md +3 -0
  164. package/generic-view/README.md +3 -0
  165. package/header-wrapper/README.md +3 -0
  166. package/icon-picker/README.md +3 -0
  167. package/input/README.md +3 -0
  168. package/input-switch/README.md +3 -0
  169. package/input-with-icon/README.md +3 -0
  170. package/label-type/README.md +3 -0
  171. package/loading-skeletons/README.md +3 -0
  172. package/odata-query-builder/README.md +3 -0
  173. package/package.json +264 -0
  174. package/query-type/README.md +3 -0
  175. package/radio/README.md +3 -0
  176. package/repeat/README.md +3 -0
  177. package/select/README.md +3 -0
  178. package/sidebar-cards/README.md +3 -0
  179. package/sidebar-toggles/README.md +3 -0
  180. package/styles.css +2 -0
  181. package/tabs/README.md +3 -0
  182. package/tag-type/README.md +3 -0
  183. package/text-editor/README.md +3 -0
  184. package/textarea/README.md +3 -0
  185. package/types/es.framework-ng.ui.core-attachments.d.ts +38 -0
  186. package/types/es.framework-ng.ui.core-base-crud.d.ts +74 -0
  187. package/types/es.framework-ng.ui.core-breadcrumb.d.ts +17 -0
  188. package/types/es.framework-ng.ui.core-checkbox.d.ts +33 -0
  189. package/types/es.framework-ng.ui.core-collapsible.d.ts +18 -0
  190. package/types/es.framework-ng.ui.core-color-picker.d.ts +16 -0
  191. package/types/es.framework-ng.ui.core-column-settings-popover.d.ts +46 -0
  192. package/types/es.framework-ng.ui.core-custom-switch.d.ts +19 -0
  193. package/types/es.framework-ng.ui.core-datepicker.d.ts +23 -0
  194. package/types/es.framework-ng.ui.core-deactivation-reason.d.ts +32 -0
  195. package/types/es.framework-ng.ui.core-excel-import.d.ts +69 -0
  196. package/types/es.framework-ng.ui.core-filter-templates.d.ts +57 -0
  197. package/types/es.framework-ng.ui.core-form-button.d.ts +17 -0
  198. package/types/es.framework-ng.ui.core-form-field.d.ts +30 -0
  199. package/types/es.framework-ng.ui.core-form-template.d.ts +16 -0
  200. package/types/es.framework-ng.ui.core-formly-avatar-image.d.ts +18 -0
  201. package/types/es.framework-ng.ui.core-formly-avatar-label.d.ts +36 -0
  202. package/types/es.framework-ng.ui.core-formly-button-selector.d.ts +35 -0
  203. package/types/es.framework-ng.ui.core-formly-button.d.ts +20 -0
  204. package/types/es.framework-ng.ui.core-formly-presets.d.ts +37 -0
  205. package/types/es.framework-ng.ui.core-formly-prime-icon-picker.d.ts +45 -0
  206. package/types/es.framework-ng.ui.core-formly-split-button.d.ts +23 -0
  207. package/types/es.framework-ng.ui.core-formly-ui-all.d.ts +40 -0
  208. package/types/es.framework-ng.ui.core-formly-ui.d.ts +13 -0
  209. package/types/es.framework-ng.ui.core-formly-username-with-domain.d.ts +19 -0
  210. package/types/es.framework-ng.ui.core-generic-assets.d.ts +10 -0
  211. package/types/es.framework-ng.ui.core-generic-autocomplete.d.ts +41 -0
  212. package/types/es.framework-ng.ui.core-generic-button.d.ts +39 -0
  213. package/types/es.framework-ng.ui.core-generic-card.d.ts +93 -0
  214. package/types/es.framework-ng.ui.core-generic-crud-table.d.ts +293 -0
  215. package/types/es.framework-ng.ui.core-generic-dialog.d.ts +93 -0
  216. package/types/es.framework-ng.ui.core-generic-errormessage.d.ts +17 -0
  217. package/types/es.framework-ng.ui.core-generic-formly-fields.d.ts +26 -0
  218. package/types/es.framework-ng.ui.core-generic-loadingspinner.d.ts +16 -0
  219. package/types/es.framework-ng.ui.core-generic-report-tabs.d.ts +21 -0
  220. package/types/es.framework-ng.ui.core-generic-report.d.ts +121 -0
  221. package/types/es.framework-ng.ui.core-generic-search-advanced.d.ts +305 -0
  222. package/types/es.framework-ng.ui.core-generic-search.d.ts +36 -0
  223. package/types/es.framework-ng.ui.core-generic-selector.d.ts +99 -0
  224. package/types/es.framework-ng.ui.core-generic-table.d.ts +84 -0
  225. package/types/es.framework-ng.ui.core-generic-view.d.ts +97 -0
  226. package/types/es.framework-ng.ui.core-header-wrapper.d.ts +15 -0
  227. package/types/es.framework-ng.ui.core-icon-picker.d.ts +30 -0
  228. package/types/es.framework-ng.ui.core-input-switch.d.ts +16 -0
  229. package/types/es.framework-ng.ui.core-input-with-icon.d.ts +17 -0
  230. package/types/es.framework-ng.ui.core-input.d.ts +47 -0
  231. package/types/es.framework-ng.ui.core-label-type.d.ts +38 -0
  232. package/types/es.framework-ng.ui.core-loading-skeletons.d.ts +17 -0
  233. package/types/es.framework-ng.ui.core-odata-query-builder.d.ts +87 -0
  234. package/types/es.framework-ng.ui.core-query-type.d.ts +37 -0
  235. package/types/es.framework-ng.ui.core-radio.d.ts +38 -0
  236. package/types/es.framework-ng.ui.core-repeat.d.ts +31 -0
  237. package/types/es.framework-ng.ui.core-select.d.ts +42 -0
  238. package/types/es.framework-ng.ui.core-sidebar-cards.d.ts +25 -0
  239. package/types/es.framework-ng.ui.core-sidebar-toggles.d.ts +25 -0
  240. package/types/es.framework-ng.ui.core-tabs.d.ts +17 -0
  241. package/types/es.framework-ng.ui.core-tag-type.d.ts +34 -0
  242. package/types/es.framework-ng.ui.core-text-editor.d.ts +16 -0
  243. package/types/es.framework-ng.ui.core-textarea.d.ts +36 -0
  244. package/types/es.framework-ng.ui.core-wrappers.d.ts +36 -0
  245. package/types/es.framework-ng.ui.core.d.ts +8 -0
  246. package/wrappers/README.md +3 -0
@@ -0,0 +1,2995 @@
1
+ import * as i0 from '@angular/core';
2
+ import { NgModule, inject, Injectable, EventEmitter, Input, Output, Component, ChangeDetectorRef } from '@angular/core';
3
+ import * as i1$2 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i1$1 from '@angular/forms';
6
+ import { FormGroup, ReactiveFormsModule, FormsModule, FormControl, FormArray } from '@angular/forms';
7
+ import { FormlyForm, FieldType, FormlyField, provideFormlyConfig } from '@ngx-formly/core';
8
+ import { TranslatePipe } from '@es.framework/ng.core/pipes';
9
+ import { ToolbarModule } from 'primeng/toolbar';
10
+ import * as i2 from 'primeng/button';
11
+ import { ButtonModule } from 'primeng/button';
12
+ import { InputIconModule } from 'primeng/inputicon';
13
+ import { IconFieldModule } from 'primeng/iconfield';
14
+ import * as i4 from 'primeng/inputtext';
15
+ import { InputTextModule } from 'primeng/inputtext';
16
+ import * as i5 from 'primeng/drawer';
17
+ import { DrawerModule } from 'primeng/drawer';
18
+ import { LocalizationService, QueryParser } from '@es.framework/ng.core/services';
19
+ import * as i7 from 'primeng/inputgroupaddon';
20
+ import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
21
+ import * as i6 from 'primeng/inputgroup';
22
+ import { InputGroupModule } from 'primeng/inputgroup';
23
+ import * as i8 from 'primeng/badge';
24
+ import { BadgeModule } from 'primeng/badge';
25
+ import * as i1 from 'primeng/api';
26
+ import * as i3 from 'primeng/select';
27
+ import { SelectModule } from 'primeng/select';
28
+ import * as i5$1 from 'primeng/dragdrop';
29
+ import { DragDropModule } from 'primeng/dragdrop';
30
+ import * as i6$1 from 'primeng/tooltip';
31
+ import { TooltipModule } from 'primeng/tooltip';
32
+ import * as i6$2 from 'primeng/listbox';
33
+ import { ListboxModule } from 'primeng/listbox';
34
+ import * as i2$1 from 'primeng/checkbox';
35
+ import { CheckboxModule } from 'primeng/checkbox';
36
+ import { DatePickerModule } from 'primeng/datepicker';
37
+ import { RadioButtonModule } from 'primeng/radiobutton';
38
+ import { MenuModule } from 'primeng/menu';
39
+ import * as i5$2 from 'primeng/popover';
40
+ import { PopoverModule } from 'primeng/popover';
41
+ import * as i1$3 from 'primeng/tabs';
42
+ import { TabsModule } from 'primeng/tabs';
43
+
44
+ class GenericSearchAdvancedModule {
45
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GenericSearchAdvancedModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
46
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: GenericSearchAdvancedModule, imports: [CommonModule] });
47
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GenericSearchAdvancedModule, imports: [CommonModule] });
48
+ }
49
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GenericSearchAdvancedModule, decorators: [{
50
+ type: NgModule,
51
+ args: [{
52
+ imports: [CommonModule],
53
+ }]
54
+ }] });
55
+
56
+ // query-builder.service.ts
57
+ class QueryBuilderService {
58
+ translate = inject(LocalizationService);
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
+ };
71
+ buildQueryUIFields(fields, enableSelect, enableGroup) {
72
+ return [
73
+ {
74
+ type: 'tab-type',
75
+ props: {
76
+ tabViewClass: 'advanced-query-tabs',
77
+ scrollable: false,
78
+ lazy: true,
79
+ activeIndex: 0
80
+ },
81
+ fieldGroup: this.getQueryTabs(fields, enableSelect, enableGroup)
82
+ }
83
+ ];
84
+ }
85
+ getQueryTabs(fields, enableSelect, enableGroup) {
86
+ const tabs = [
87
+ {
88
+ type: 'group',
89
+ props: {
90
+ tabLabel: 'FILTERS',
91
+ // label: 'FILTERS',
92
+ leftIcon: 'pi pi-filter',
93
+ badge: (field) => {
94
+ const fieldGroup = (field?.fieldGroup ?? [])[0];
95
+ return (fieldGroup?.model?.advancedFilters ?? [])[0]?.conditions?.length || 0;
96
+ },
97
+ },
98
+ fieldGroup: this.getFilterTabFields(fields)
99
+ },
100
+ {
101
+ type: 'group',
102
+ props: {
103
+ tabLabel: 'SORTING',
104
+ // label: 'SORTING',
105
+ leftIcon: 'pi pi-sort-alt',
106
+ badge: (field) => {
107
+ const fieldGroup = (field?.fieldGroup ?? [])[0];
108
+ return (fieldGroup?.model?.sorting ?? [])?.length || 0;
109
+ },
110
+ },
111
+ fieldGroup: this.getSortingTabFields(fields)
112
+ },
113
+ enableGroup && {
114
+ type: 'group',
115
+ props: {
116
+ tabLabel: 'GROUPING',
117
+ // label: 'GROUPING',
118
+ leftIcon: 'pi pi-table',
119
+ badge: (field) => {
120
+ const fieldGroup = (field?.fieldGroup ?? [])[0];
121
+ return (fieldGroup?.model?.grouping ?? [])?.length || 0;
122
+ },
123
+ },
124
+ fieldGroup: this.getGroupingTabFields(fields)
125
+ },
126
+ enableSelect && {
127
+ type: 'group',
128
+ props: {
129
+ tabLabel: 'COLUMNS',
130
+ // label: 'COLUMNS',
131
+ leftIcon: 'pi pi-list',
132
+ badge: (field) => {
133
+ const fieldGroup = (field?.fieldGroup ?? [])[0];
134
+ const columns = fieldGroup?.model?.columns;
135
+ // Handle both array and object formats for columns
136
+ if (Array.isArray(columns)) {
137
+ return columns.filter((col) => col?.isVisible)?.length || 0;
138
+ }
139
+ else if (columns && typeof columns === 'object') {
140
+ // If columns is an object, count the visible ones
141
+ return Object.values(columns).filter((col) => col?.isVisible)?.length || 0;
142
+ }
143
+ return 0;
144
+ },
145
+ },
146
+ fieldGroup: this.getColumnsTabFields(fields)
147
+ },
148
+ {
149
+ type: 'group',
150
+ props: {
151
+ tabLabel: 'PAGINATION',
152
+ // label: 'PAGINATION',
153
+ leftIcon: 'pi pi-cog',
154
+ badge: (field) => {
155
+ return field?.model?.pagination && field?.model?.pagination?.top ? 1 : 0;
156
+ },
157
+ },
158
+ fieldGroup: this.getPaginationTabFields()
159
+ }
160
+ ];
161
+ return tabs.filter(Boolean);
162
+ }
163
+ getFilterTabFields(fields) {
164
+ return [
165
+ {
166
+ key: 'advancedFilters',
167
+ type: 'query-builder',
168
+ props: {
169
+ label: 'فلاتر متقدمة',
170
+ description: 'أنشئ فلاتر معقدة باستخدام مشغلين منطقيين',
171
+ fields: this.extractFilterableFields(fields)
172
+ }
173
+ }
174
+ ];
175
+ }
176
+ getSortingTabFields(fields) {
177
+ return [
178
+ {
179
+ key: 'sorting',
180
+ type: 'sort-builder',
181
+ props: {
182
+ label: 'ترتيب النتائج',
183
+ description: 'حدد أولوية وعرض ترتيب البيانات',
184
+ fields: this.extractSortableFields(fields)
185
+ }
186
+ }
187
+ ];
188
+ }
189
+ getGroupingTabFields(fields) {
190
+ return [
191
+ {
192
+ key: 'grouping',
193
+ type: 'group-builder',
194
+ props: {
195
+ label: 'تجميع البيانات',
196
+ description: 'قم بتجميع البيانات حسب الحقول المطلوبة',
197
+ fields: this.extractGroupableFields(fields),
198
+ maxGroups: 3
199
+ }
200
+ }
201
+ ];
202
+ }
203
+ getColumnsTabFields(fields) {
204
+ return [
205
+ {
206
+ key: 'columns',
207
+ type: 'columns-builder',
208
+ props: {
209
+ label: 'إدارة الأعمدة',
210
+ description: 'اختر الحقول المراد عرضها وتخصيص التجميع',
211
+ fields: this.extractSelectableFields(fields)
212
+ }
213
+ }
214
+ ];
215
+ }
216
+ getPaginationTabFields() {
217
+ return [
218
+ {
219
+ key: 'pagination',
220
+ type: 'group',
221
+ props: {
222
+ label: '',
223
+ description: '',
224
+ collapsible: false,
225
+ showFieldCount: true,
226
+ variant: 'card'
227
+ },
228
+ fieldGroup: [
229
+ {
230
+ key: 'top',
231
+ type: 'input',
232
+ props: {
233
+ type: 'number',
234
+ label: 'الحد الأقصى للسجلات',
235
+ min: 1,
236
+ max: 1000,
237
+ description: 'أقصى عدد للسجلات المراد عرضها'
238
+ }
239
+ },
240
+ {
241
+ key: 'skip',
242
+ type: 'input',
243
+ props: {
244
+ type: 'number',
245
+ label: 'تخطي السجلات',
246
+ min: 0,
247
+ description: 'عدد السجلات التي سيتم تخطيها'
248
+ }
249
+ },
250
+ {
251
+ key: 'includeCount',
252
+ type: 'checkbox',
253
+ props: {
254
+ label: 'تضمين العدد الإجمالي',
255
+ description: 'تضمين عدد السجلات الإجمالي في الاستجابة'
256
+ }
257
+ }
258
+ ]
259
+ }
260
+ ];
261
+ }
262
+ extractFilterableFields(fields) {
263
+ const filterableFields = [];
264
+ this.traverseFields(fields, (field) => {
265
+ if (field.key && field.props && ((field.props['filter'] && field.props['filter'].hidden == false) || !field.props['filter']) && !field.props?.hidden) {
266
+ const fieldType = this.mapFieldType(field.type);
267
+ const fieldConfig = this.fieldTypeConfig[fieldType] || this.fieldTypeConfig.input;
268
+ filterableFields.push({
269
+ key: field.props['entityKey'] ? field.props['entityKey'] : field.key,
270
+ label: this.translate.instant(field.props.label || field.key),
271
+ type: fieldType,
272
+ props: { ...field.props },
273
+ operators: fieldConfig.operators,
274
+ defaultOperator: fieldConfig.defaultOperator,
275
+ defaultValue: fieldConfig.defaultValue
276
+ });
277
+ }
278
+ });
279
+ return filterableFields;
280
+ }
281
+ extractSortableFields(fields) {
282
+ const sortableFields = [];
283
+ this.traverseFields(fields, (field) => {
284
+ if (field.key && field.props && ((field.props['sort'] && field.props['sort'].hidden == false) || !field.props['sort']) && !field.props?.hidden) {
285
+ sortableFields.push({
286
+ key: field.props['entityKey'] ? field.props['entityKey'] : field.key,
287
+ label: this.translate.instant(field.props.label || field.key)
288
+ });
289
+ }
290
+ });
291
+ return sortableFields;
292
+ }
293
+ extractGroupableFields(fields) {
294
+ const groupableFields = [];
295
+ this.traverseFields(fields, (field) => {
296
+ if (field.key && field.props && ((field.props['group'] && field.props['group'].hidden == false) || !field.props['group']) && !field.props?.hidden) {
297
+ groupableFields.push({
298
+ key: field.props['entityKey'] ? field.props['entityKey'] : field.key,
299
+ label: this.translate.instant(field.props.label || field.key),
300
+ dataType: this.getDataType(field.type)
301
+ });
302
+ }
303
+ });
304
+ return groupableFields;
305
+ }
306
+ extractSelectableFields(fields) {
307
+ const tempFields = [];
308
+ this.traverseFields(fields, (field) => {
309
+ if (field.key &&
310
+ field.props &&
311
+ // ((field.props['select'] && field.props['select'].hidden === false) || !field.props['select']) &&
312
+ ((field.props['report'] && field.props['report'].hidden === false) || !field.props['report']) &&
313
+ !field.props?.hidden) {
314
+ const isNumeric = ['number', 'currency'].includes(field.type);
315
+ const tableConfig = field.props['table'] ?? {};
316
+ const mergedProps = { ...field.props, ...(tableConfig.props ?? {}) };
317
+ const order = tableConfig.order ??
318
+ tableConfig.props?.order ??
319
+ mergedProps['order'] ??
320
+ Number.MAX_SAFE_INTEGER;
321
+ tempFields.push({
322
+ key: field.props['entityKey'] ? field.props['entityKey'] : field.key,
323
+ label: this.translate.instant(field.props.label || field.key),
324
+ dataType: this.getDataType(field.type),
325
+ isNumeric,
326
+ defaultVisible: field.props['defaultVisible'] !== false,
327
+ aggregatable: isNumeric && field.props['aggregatable'] !== false,
328
+ order
329
+ });
330
+ }
331
+ });
332
+ return tempFields
333
+ .sort((a, b) => a.order - b.order)
334
+ .map(({ order, ...rest }) => rest); // remove order after sorting
335
+ }
336
+ traverseFields(fields, callback) {
337
+ fields.forEach(field => {
338
+ callback(field);
339
+ if (field.fieldGroup) {
340
+ this.traverseFields(field.fieldGroup, callback);
341
+ }
342
+ });
343
+ }
344
+ getAppliedFiltersCount(model) {
345
+ const breakdown = {
346
+ filters: 0,
347
+ sorting: 0,
348
+ grouping: 0,
349
+ columns: 0,
350
+ pagination: 0,
351
+ expand: 0
352
+ };
353
+ // Advanced filters
354
+ if (model?.advancedFilters && Array.isArray(model.advancedFilters)) {
355
+ model.advancedFilters.forEach((filterGroup) => {
356
+ if (filterGroup.conditions && Array.isArray(filterGroup.conditions)) {
357
+ breakdown.filters += filterGroup.conditions.length;
358
+ }
359
+ });
360
+ }
361
+ // Sorting
362
+ if (model?.sorting && Array.isArray(model.sorting)) {
363
+ breakdown.sorting = model.sorting.length;
364
+ }
365
+ // Grouping
366
+ if (model?.grouping && Array.isArray(model.grouping)) {
367
+ breakdown.grouping = model.grouping.length;
368
+ }
369
+ // Columns
370
+ if (model?.columns) {
371
+ if (Array.isArray(model.columns)) {
372
+ breakdown.columns = model.columns.filter((col) => col.isVisible).length;
373
+ }
374
+ else if (typeof model.columns === 'object') {
375
+ breakdown.columns = Object.values(model.columns).filter((col) => col?.isVisible).length;
376
+ }
377
+ }
378
+ // Pagination
379
+ if (model?.pagination) {
380
+ if (model.pagination.top)
381
+ breakdown.pagination += 1;
382
+ if (model.pagination.skip)
383
+ breakdown.pagination += 1;
384
+ if (model.pagination.includeCount)
385
+ breakdown.pagination += 1;
386
+ }
387
+ // Expand
388
+ if (model?.expand && Array.isArray(model.expand)) {
389
+ breakdown.expand = model.expand.length;
390
+ }
391
+ return {
392
+ total: Object.values(breakdown).reduce((sum, count) => sum + count, 0),
393
+ breakdown
394
+ };
395
+ }
396
+ mapFieldType(type) {
397
+ const typeMap = {
398
+ 'input': 'input',
399
+ 'number': 'number',
400
+ 'datepicker': 'datepicker',
401
+ 'select': 'select',
402
+ 'checkbox': 'checkbox',
403
+ 'generic-selector': 'generic-selector',
404
+ 'switch': 'switch',
405
+ 'radio': 'radio',
406
+ 'textarea': 'textarea',
407
+ 'currency': 'number'
408
+ };
409
+ return typeMap[type || 'input'] || 'input';
410
+ }
411
+ getDataType(type) {
412
+ const dataTypeMap = {
413
+ 'input': 'string',
414
+ 'number': 'number',
415
+ 'datepicker': 'date',
416
+ 'select': 'string',
417
+ 'checkbox': 'boolean',
418
+ 'generic-selector': 'string',
419
+ 'switch': 'boolean',
420
+ 'radio': 'string',
421
+ 'textarea': 'string',
422
+ 'currency': 'number'
423
+ };
424
+ return dataTypeMap[type || 'input'] || 'string';
425
+ }
426
+ getOperatorsForType(type) {
427
+ const fieldType = this.mapFieldType(type);
428
+ const config = this.fieldTypeConfig[fieldType];
429
+ return config ? config.operators : ['eq', 'ne'];
430
+ }
431
+ // Enhanced OData methods with GroupBy and Select support
432
+ buildODataFromQueryModel(model, originalFields) {
433
+ const params = {
434
+ filters: [],
435
+ orderBy: [],
436
+ groupBy: [],
437
+ select: [],
438
+ aggregates: [],
439
+ expand: model.expand || [],
440
+ includeCount: model.pagination?.includeCount || false
441
+ };
442
+ // Quick search
443
+ if (model.filter) {
444
+ const searchableFields = this.extractSelectableFields(originalFields); // TODO get all strings filters
445
+ const quickSearchConditions = searchableFields.map(field => ({
446
+ field: field.key,
447
+ operator: 'contains',
448
+ value: model.filter
449
+ }));
450
+ if (quickSearchConditions.length > 0) {
451
+ params.filters.push({
452
+ logicalOperator: 'or',
453
+ conditions: quickSearchConditions
454
+ });
455
+ }
456
+ }
457
+ // Advanced filters
458
+ if (model.advancedFilters && Array.isArray(model.advancedFilters)) {
459
+ params.filters.push(...model.advancedFilters);
460
+ }
461
+ // Sorting
462
+ if (model.sorting && Array.isArray(model.sorting)) {
463
+ params.orderBy = model.sorting
464
+ .filter((sort) => sort.field && sort.direction)
465
+ .map((sort) => ({
466
+ field: sort.field,
467
+ direction: sort.direction
468
+ }));
469
+ }
470
+ // Grouping
471
+ if (model.grouping && Array.isArray(model.grouping)) {
472
+ params.groupBy = model.grouping
473
+ .filter((group) => group.field)
474
+ .map((group, index) => ({
475
+ propertyName: group.field,
476
+ displayName: group.displayName || group.field,
477
+ showTotal: group.showTotal !== false,
478
+ order: index
479
+ }));
480
+ }
481
+ // Columns/Select - Handle both array and object formats
482
+ // extractSelectableFields
483
+ if (model.columns) {
484
+ let visibleColumns = [];
485
+ // Handle array format: [{field: 'name', isVisible: true, ...}, ...]
486
+ if (Array.isArray(model.columns)) {
487
+ visibleColumns = model.columns.filter((col) => col.isVisible);
488
+ }
489
+ // Handle object format: {field1: {isVisible: true, displayName: 'Name', ...}, field2: {...}}
490
+ else if (typeof model.columns === 'object') {
491
+ visibleColumns = Object.entries(model.columns)
492
+ .filter(([key, col]) => col.isVisible)
493
+ .map(([key, col]) => ({
494
+ field: key, // Use the object key as field name
495
+ ...col // Spread the column properties
496
+ }));
497
+ }
498
+ // Process visible columns for select and aggregates
499
+ if (visibleColumns.length > 0) {
500
+ params.select = visibleColumns.map((col, index) => ({
501
+ propertyName: col.field || col.key, // Support both 'field' and 'key' properties
502
+ displayName: col.displayName || col.field || col.key || col.label,
503
+ aggregateFunction: col.aggregateFunction,
504
+ isVisible: true,
505
+ order: index
506
+ }));
507
+ // Extract aggregates from columns with aggregate functions
508
+ const aggregateColumns = visibleColumns.filter((col) => col.aggregateFunction);
509
+ params.aggregates = aggregateColumns.map((col) => ({
510
+ propertyName: col.field || col.key,
511
+ aggregateFunction: col.aggregateFunction,
512
+ alias: `${col.aggregateFunction}_${col.field || col.key}`
513
+ }));
514
+ }
515
+ }
516
+ else {
517
+ // Fallback: auto-generate from formly fields
518
+ const selectableFields = this.extractSelectableFields(originalFields);
519
+ // Ensure a column for the ID field exists
520
+ // if (!selectableFields.some(f => f.key === this.idField)) {
521
+ // selectableFields.unshift({
522
+ // key: this.idField,
523
+ // label: this.translate.instant(this.idField),
524
+ // dataType: 'number', // or the correct type
525
+ // isNumeric: false,
526
+ // defaultVisible: true,
527
+ // aggregatable: false
528
+ // });
529
+ // }
530
+ params.select = selectableFields.map((f, i) => ({
531
+ propertyName: f.key,
532
+ displayName: f.label,
533
+ isVisible: f.defaultVisible,
534
+ order: i
535
+ }));
536
+ params.aggregates = selectableFields
537
+ .filter(f => f.aggregatable)
538
+ .map(f => ({
539
+ propertyName: f.key,
540
+ aggregateFunction: 'sum',
541
+ alias: `sum_${f.key}`
542
+ }));
543
+ }
544
+ // Pagination
545
+ if (model.pagination) {
546
+ if (model.pagination.top) {
547
+ params.top = Number(model.pagination.top);
548
+ }
549
+ if (model.pagination.skip) {
550
+ params.skip = Number(model.pagination.skip);
551
+ }
552
+ if (model.pagination.includeCount !== undefined) {
553
+ params.includeCount = model.pagination.includeCount;
554
+ }
555
+ }
556
+ return params;
557
+ }
558
+ toODataQueryString(params) {
559
+ return QueryParser.toString(params);
560
+ }
561
+ parseODataQuery(queryString) {
562
+ return QueryParser.parse(queryString);
563
+ }
564
+ // Helper method to get field configuration
565
+ getFieldConfig(fieldKey, fields) {
566
+ return fields.find(f => f.key === fieldKey);
567
+ }
568
+ // Helper to get available aggregate functions for numeric fields
569
+ getAggregateFunctions() {
570
+ return [
571
+ { label: 'مجموع', value: 'sum' },
572
+ { label: 'متوسط', value: 'avg' },
573
+ { label: 'عدد', value: 'count' },
574
+ { label: 'الحد الأدنى', value: 'min' },
575
+ { label: 'الحد الأقصى', value: 'max' }
576
+ ];
577
+ }
578
+ queryParamsToFormModel(params) {
579
+ const model = {
580
+ advancedFilters: [],
581
+ sorting: [],
582
+ grouping: [],
583
+ columns: [],
584
+ pagination: {},
585
+ expand: [],
586
+ filter: ''
587
+ };
588
+ // Extract quick search from filters
589
+ const orFilterGroup = params.filters.find(fg => fg.logicalOperator === 'or' &&
590
+ fg.conditions?.every(c => c.operator === 'contains' && c.value === fg.conditions[0]?.value));
591
+ if (orFilterGroup?.conditions?.length) {
592
+ model.filter = orFilterGroup.conditions[0].value;
593
+ // Remove the quick search from advanced filters
594
+ params.filters = params.filters.filter(fg => fg !== orFilterGroup);
595
+ }
596
+ // Advanced filters
597
+ if (params.filters.length > 0) {
598
+ model.advancedFilters = params.filters;
599
+ }
600
+ // Sorting
601
+ if (params.orderBy && params.orderBy.length > 0) {
602
+ model.sorting = params.orderBy.map(ob => ({
603
+ field: ob.field,
604
+ direction: ob.direction
605
+ }));
606
+ }
607
+ // Grouping
608
+ if (params.groupBy && params.groupBy.length > 0) {
609
+ model.grouping = params.groupBy.map(gb => ({
610
+ field: gb.propertyName,
611
+ displayName: gb.displayName,
612
+ showTotal: gb.showTotal,
613
+ order: gb.order
614
+ }));
615
+ }
616
+ // Columns/Select - Convert to array format
617
+ if (params.select && params.select.length > 0) {
618
+ model.columns = params.select.map(sc => {
619
+ const col = {
620
+ field: sc.propertyName,
621
+ isVisible: sc.isVisible !== false,
622
+ displayName: sc.displayName || sc.propertyName,
623
+ order: sc.order || 0
624
+ };
625
+ // Add aggregate function if this column has an aggregate
626
+ if (params.aggregates) {
627
+ const aggregate = params.aggregates.find(agg => agg.propertyName === sc.propertyName);
628
+ if (aggregate) {
629
+ col.aggregateFunction = aggregate.aggregateFunction;
630
+ }
631
+ }
632
+ return col;
633
+ });
634
+ // Sort columns by order
635
+ model.columns.sort((a, b) => (a.order || 0) - (b.order || 0));
636
+ }
637
+ // Aggregates (if not already handled via columns)
638
+ if (params.aggregates && params.aggregates.length > 0) {
639
+ // Ensure aggregates are linked to columns
640
+ if (!model.columns) {
641
+ model.columns = [];
642
+ }
643
+ params.aggregates.forEach(agg => {
644
+ const existingColumn = model.columns.find((c) => c.field === agg.propertyName);
645
+ if (!existingColumn) {
646
+ model.columns.push({
647
+ field: agg.propertyName,
648
+ isVisible: false, // Aggregate-only columns are typically not visible in select
649
+ displayName: agg.propertyName,
650
+ aggregateFunction: agg.aggregateFunction
651
+ });
652
+ }
653
+ });
654
+ }
655
+ // Pagination
656
+ if (params.top !== undefined || params.skip !== undefined || params.includeCount !== undefined) {
657
+ model.pagination = {
658
+ top: params.top,
659
+ skip: params.skip,
660
+ includeCount: params.includeCount
661
+ };
662
+ }
663
+ // Expand
664
+ if (params.expand && params.expand.length > 0) {
665
+ model.expand = params.expand;
666
+ }
667
+ return model;
668
+ }
669
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: QueryBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
670
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: QueryBuilderService, providedIn: 'root' });
671
+ }
672
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: QueryBuilderService, decorators: [{
673
+ type: Injectable,
674
+ args: [{
675
+ providedIn: 'root'
676
+ }]
677
+ }] });
678
+
679
+ // generic-search.component.ts
680
+ class GenericSearchAdvanced {
681
+ translate = inject(LocalizationService);
682
+ queryBuilder = inject(QueryBuilderService);
683
+ drawerVisible = false;
684
+ search = new EventEmitter();
685
+ paginationChange = new EventEmitter();
686
+ odataSearch = new EventEmitter();
687
+ form = new FormGroup({});
688
+ model = {};
689
+ options = {};
690
+ fields = [];
691
+ fields_ = [];
692
+ enableSelect = false;
693
+ enableGroup = false;
694
+ odataConfig = {};
695
+ ngOnInit() {
696
+ this.buildQueryUIFields();
697
+ }
698
+ get filterCount() {
699
+ const filters = ((this.model?.advancedFilters ?? [])[0]?.conditions?.length || 0) +
700
+ ((this.model?.sorting ?? [])?.length || 0) +
701
+ (this.model?.pagination && this.model?.pagination?.top ? 1 : 0);
702
+ return filters;
703
+ }
704
+ buildQueryUIFields() {
705
+ this.fields_ = this.queryBuilder.buildQueryUIFields(this.fields, this.enableSelect, this.enableGroup);
706
+ }
707
+ onSubmit() {
708
+ // Use the new QueryBuilderService methods
709
+ const odataParams = this.queryBuilder.buildODataFromQueryModel(this.model, this.fields);
710
+ // Generate query string using the unified method
711
+ const queryString = this.queryBuilder.toODataQueryString(odataParams);
712
+ if (this.model?.pagination && this.model?.pagination?.top) {
713
+ this.paginationChange.emit({
714
+ top: this.model?.pagination?.top,
715
+ // skip:,
716
+ });
717
+ }
718
+ this.search.emit({
719
+ filter: this.model['filter'],
720
+ query: queryString,
721
+ // sorting:,
722
+ // top:,
723
+ // skip:,
724
+ });
725
+ }
726
+ onReset() {
727
+ this.model = {};
728
+ this.form.reset();
729
+ // this.paginationChange.emit({
730
+ // top:10,
731
+ // // skip:,
732
+ // });
733
+ // this.search.emit(this.model);
734
+ const odataParams = this.queryBuilder.buildODataFromQueryModel(this.model, this.fields);
735
+ // this.odataSearch.emit(odataParams);
736
+ // Generate query string using the unified method
737
+ const queryString = this.queryBuilder.toODataQueryString(odataParams);
738
+ this.search.emit({
739
+ filter: this.model['filter'],
740
+ query: queryString,
741
+ // sorting:,
742
+ top: 10,
743
+ // skip:,
744
+ });
745
+ }
746
+ // Toggle drawer visibility
747
+ toggleDrawer() {
748
+ this.drawerVisible = !this.drawerVisible;
749
+ }
750
+ // Close drawer
751
+ closeDrawer() {
752
+ this.drawerVisible = false;
753
+ }
754
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GenericSearchAdvanced, deps: [], target: i0.ɵɵFactoryTarget.Component });
755
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GenericSearchAdvanced, isStandalone: true, selector: "lib-generic-search-advanced", inputs: { model: "model", fields: "fields", enableSelect: "enableSelect", enableGroup: "enableGroup", odataConfig: "odataConfig" }, outputs: { search: "search", paginationChange: "paginationChange", odataSearch: "odataSearch" }, ngImport: i0, template: "<div class=\"flex flex-wrap items-center gap-3 w-full\">\n <!-- Search Input with Icon -->\n <p-inputgroup class=\"w-full\">\n <input\n pInputText\n type=\"text\"\n [(ngModel)]=\"model['filter']\"\n placeholder=\"{{ 'SEARCH' | translate }}\"\n (keyup.enter)=\"onSubmit()\"\n />\n\n\n <p-inputgroup-addon>\n <p-button\n icon=\"pi pi-search\"\n severity=\"secondary\"\n (click)=\"onSubmit()\"\n ></p-button>\n </p-inputgroup-addon>\n <p-inputgroup-addon>\n <div class=\"relative inline-flex\">\n <p-button\n icon=\"pi pi-filter\"\n severity=\"secondary\"\n (onClick)=\"drawerVisible = true\">\n </p-button>\n\n @if (filterCount > 0) {\n <p-badge\n [value]=\"filterCount\"\n severity=\"warn\"\n size=\"small\"\n class=\"absolute -top-0 -right-0\">\n </p-badge>\n }\n </div>\n </p-inputgroup-addon>\n\n @if (model['filter'] || filterCount > 0) {\n <p-inputgroup-addon>\n <p-button\n icon=\"pi pi-times\"\n severity=\"danger\"\n (click)=\"model['filter']=''; onReset()\"\n ></p-button>\n </p-inputgroup-addon>\n }\n </p-inputgroup>\n\n\n </div>\n\n <!-- Drawer for Advanced Multi-field Search -->\n @if(drawerVisible){\n <p-drawer\n [(visible)]=\"drawerVisible\"\n position=\"right\"\n [styleClass]=\"'!w-full md:!w-80 lg:!w-[40rem] !h-full' \"\n [modal]=\"true\"\n [dismissible]=\"true\"\n styleClass=\"p-4 w-full max-w-md max-h-[90vh] flex flex-col\"\n >\n <!-- Entire form wrapper -->\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit(); drawerVisible=false\" class=\"flex flex-col flex-1\">\n <!-- Scrollable Form -->\n <div class=\"flex-1 overflow-auto\">\n <formly-form\n [form]=\"form\"\n [fields]=\"fields_\"\n [model]=\"model\"\n [options]=\"options\"\n >\n </formly-form>\n </div>\n\n </form>\n <!-- Action Buttons -->\n <ng-template pTemplate=\"footer\">\n <div class=\"flex justify-end mt-2 space-x-2 flex-none\">\n <button\n type=\"submit\"\n pButton size=\"small\"\n (click)=\" onSubmit(); drawerVisible=false\"\n label=\"{{ 'SEARCH' | translate }}\">\n </button>\n <button\n type=\"button\"\n pButton\n size=\"small\"\n class=\"p-button-text\"\n (click)=\"onReset(); drawerVisible=false\"\n >\n {{ 'CLEAR' | translate }}\n </button>\n </div>\n </ng-template>\n </p-drawer>\n\n }\n", dependencies: [{ kind: "component", type: FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }, { kind: "ngmodule", type: ToolbarModule }, { kind: "directive", type: i1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$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: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$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: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "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: InputGroupModule }, { kind: "component", type: i6.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "component", type: i7.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i8.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
756
+ }
757
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GenericSearchAdvanced, decorators: [{
758
+ type: Component,
759
+ args: [{ selector: 'lib-generic-search-advanced', standalone: true, imports: [
760
+ FormlyForm,
761
+ TranslatePipe,
762
+ ToolbarModule,
763
+ ButtonModule,
764
+ ReactiveFormsModule,
765
+ FormsModule,
766
+ InputIconModule,
767
+ IconFieldModule,
768
+ InputTextModule,
769
+ DrawerModule,
770
+ InputGroupModule,
771
+ InputGroupAddonModule,
772
+ BadgeModule
773
+ ], template: "<div class=\"flex flex-wrap items-center gap-3 w-full\">\n <!-- Search Input with Icon -->\n <p-inputgroup class=\"w-full\">\n <input\n pInputText\n type=\"text\"\n [(ngModel)]=\"model['filter']\"\n placeholder=\"{{ 'SEARCH' | translate }}\"\n (keyup.enter)=\"onSubmit()\"\n />\n\n\n <p-inputgroup-addon>\n <p-button\n icon=\"pi pi-search\"\n severity=\"secondary\"\n (click)=\"onSubmit()\"\n ></p-button>\n </p-inputgroup-addon>\n <p-inputgroup-addon>\n <div class=\"relative inline-flex\">\n <p-button\n icon=\"pi pi-filter\"\n severity=\"secondary\"\n (onClick)=\"drawerVisible = true\">\n </p-button>\n\n @if (filterCount > 0) {\n <p-badge\n [value]=\"filterCount\"\n severity=\"warn\"\n size=\"small\"\n class=\"absolute -top-0 -right-0\">\n </p-badge>\n }\n </div>\n </p-inputgroup-addon>\n\n @if (model['filter'] || filterCount > 0) {\n <p-inputgroup-addon>\n <p-button\n icon=\"pi pi-times\"\n severity=\"danger\"\n (click)=\"model['filter']=''; onReset()\"\n ></p-button>\n </p-inputgroup-addon>\n }\n </p-inputgroup>\n\n\n </div>\n\n <!-- Drawer for Advanced Multi-field Search -->\n @if(drawerVisible){\n <p-drawer\n [(visible)]=\"drawerVisible\"\n position=\"right\"\n [styleClass]=\"'!w-full md:!w-80 lg:!w-[40rem] !h-full' \"\n [modal]=\"true\"\n [dismissible]=\"true\"\n styleClass=\"p-4 w-full max-w-md max-h-[90vh] flex flex-col\"\n >\n <!-- Entire form wrapper -->\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit(); drawerVisible=false\" class=\"flex flex-col flex-1\">\n <!-- Scrollable Form -->\n <div class=\"flex-1 overflow-auto\">\n <formly-form\n [form]=\"form\"\n [fields]=\"fields_\"\n [model]=\"model\"\n [options]=\"options\"\n >\n </formly-form>\n </div>\n\n </form>\n <!-- Action Buttons -->\n <ng-template pTemplate=\"footer\">\n <div class=\"flex justify-end mt-2 space-x-2 flex-none\">\n <button\n type=\"submit\"\n pButton size=\"small\"\n (click)=\" onSubmit(); drawerVisible=false\"\n label=\"{{ 'SEARCH' | translate }}\">\n </button>\n <button\n type=\"button\"\n pButton\n size=\"small\"\n class=\"p-button-text\"\n (click)=\"onReset(); drawerVisible=false\"\n >\n {{ 'CLEAR' | translate }}\n </button>\n </div>\n </ng-template>\n </p-drawer>\n\n }\n" }]
774
+ }], propDecorators: { search: [{
775
+ type: Output
776
+ }], paginationChange: [{
777
+ type: Output
778
+ }], odataSearch: [{
779
+ type: Output
780
+ }], model: [{
781
+ type: Input
782
+ }], fields: [{
783
+ type: Input
784
+ }], enableSelect: [{
785
+ type: Input
786
+ }], enableGroup: [{
787
+ type: Input
788
+ }], odataConfig: [{
789
+ type: Input
790
+ }] } });
791
+
792
+ // sort-builder.component.ts
793
+ class SortBuilderComponent extends FieldType {
794
+ sorts = [];
795
+ directionOptions = [
796
+ { label: 'تصاعدي (أ → ي)', value: 'asc' },
797
+ { label: 'تنازلي (ي → أ)', value: 'desc' }
798
+ ];
799
+ dragStartIndex = -1;
800
+ ngOnInit() {
801
+ if (this.formControl.value && Array.isArray(this.formControl.value)) {
802
+ this.sorts = this.formControl.value.map((sort) => ({
803
+ field: sort.field || '',
804
+ direction: sort.direction || 'asc'
805
+ }));
806
+ }
807
+ }
808
+ addSort() {
809
+ this.sorts.push({ field: '', direction: 'asc' });
810
+ this.updateValue();
811
+ }
812
+ removeSort(index) {
813
+ this.sorts.splice(index, 1);
814
+ this.updateValue();
815
+ }
816
+ moveSort(fromIndex, toIndex) {
817
+ if (toIndex >= 0 && toIndex < this.sorts.length) {
818
+ const movedItem = this.sorts.splice(fromIndex, 1)[0];
819
+ this.sorts.splice(toIndex, 0, movedItem);
820
+ this.updateValue();
821
+ }
822
+ }
823
+ onDragStart(index) {
824
+ this.dragStartIndex = index;
825
+ }
826
+ onDrop(event, dropIndex) {
827
+ if (this.dragStartIndex !== -1 && this.dragStartIndex !== dropIndex) {
828
+ this.moveSort(this.dragStartIndex, dropIndex);
829
+ }
830
+ this.dragStartIndex = -1;
831
+ }
832
+ reverseSorts() {
833
+ this.sorts.forEach(sort => {
834
+ sort.direction = sort.direction === 'asc' ? 'desc' : 'asc';
835
+ });
836
+ this.updateValue();
837
+ }
838
+ clearAll() {
839
+ this.sorts = [];
840
+ this.updateValue();
841
+ }
842
+ getAvailableFields(currentField) {
843
+ const usedFields = this.sorts.map(s => s.field).filter(f => f && f !== currentField);
844
+ return this.props['fields'].filter((field) => !usedFields.includes(field.key) || field.key === currentField);
845
+ }
846
+ getFieldLabel(fieldKey) {
847
+ const field = this.props['fields'].find((f) => f.key === fieldKey);
848
+ return field?.label || fieldKey;
849
+ }
850
+ getDirectionIcon(direction) {
851
+ return direction === 'asc' ? 'pi pi-sort-up' : 'pi pi-sort-down';
852
+ }
853
+ getDirectionColor(direction) {
854
+ return direction === 'asc' ? 'text-green-500' : 'text-red-500';
855
+ }
856
+ updateValue() {
857
+ // Filter out empty sorts and update value
858
+ const validSorts = this.sorts.filter(sort => sort.field && sort.direction);
859
+ this.formControl.setValue(validSorts);
860
+ }
861
+ // Helper to check if it's the last item
862
+ get isLast() {
863
+ return false; // This is used in template context
864
+ }
865
+ // Method to get current sort configuration
866
+ getSortConfiguration() {
867
+ return this.sorts.filter(sort => sort.field && sort.direction);
868
+ }
869
+ // Method to load sort configuration
870
+ loadSortConfiguration(sortConfig) {
871
+ if (sortConfig && Array.isArray(sortConfig)) {
872
+ this.sorts = sortConfig.map(sort => ({
873
+ field: sort.field || '',
874
+ direction: sort.direction || 'asc'
875
+ }));
876
+ this.updateValue();
877
+ }
878
+ }
879
+ // Method to check if a field is already used in sorting
880
+ isFieldUsed(fieldKey) {
881
+ return this.sorts.some(sort => sort.field === fieldKey);
882
+ }
883
+ // Get the next available field
884
+ getNextAvailableField() {
885
+ const availableFields = this.props['fields'].filter((field) => !this.isFieldUsed(field.key));
886
+ return availableFields.length > 0 ? availableFields[0].key : '';
887
+ }
888
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: SortBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
889
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: SortBuilderComponent, isStandalone: true, selector: "formly-sort-builder", usesInheritance: true, ngImport: i0, template: `<div class="sort-builder p-3 border-round border-1 surface-border">
890
+ <div class="mb-6">
891
+ <h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || ('SORTING' | translate) }}</h5>
892
+ <p class="text-gray-600 text-sm">{{ ('PRIORITY_HINT' | translate) }}</p>
893
+ </div>
894
+
895
+ <!-- Sort List -->
896
+ @if (sorts.length > 0) {
897
+ <div class="sort-list space-y-2 mb-3">
898
+ @for (sort of sorts; track sort; let i = $index; let isFirst = $first; let isLast = $last) {
899
+ <div
900
+ class="sort-row p-2 border-round surface-card"
901
+ pDraggable
902
+ pDroppable
903
+ (onDragStart)="onDragStart(i)"
904
+ (onDrop)="onDrop($event, i)"
905
+ [ngClass]="{'border-left-3 border-primary': isFirst, 'border-left-3 border-200': !isFirst}">
906
+ <div class="grid grid-cols-4 align-items-center gap-2">
907
+ <!-- Drag Handle -->
908
+ <div class="col-span-2 flex">
909
+ <button
910
+ pButton
911
+ icon="pi pi-bars"
912
+ type="button"
913
+ class="p-button-text p-button-plain cursor-move"
914
+ [pTooltip]="'DRAG_TOOLTIP' | translate"
915
+ tooltipPosition="top">
916
+ </button>
917
+ <p-select
918
+ [options]="getAvailableFields(sort.field)"
919
+ optionLabel="label"
920
+ optionValue="key"
921
+ [(ngModel)]="sort.field"
922
+ (onChange)="updateValue()"
923
+ [placeholder]="'SELECT_FIELD_SORT' | translate"
924
+ [showClear]="true"
925
+ appendTo="body"
926
+ styleClass="w-full">
927
+ </p-select>
928
+ </div>
929
+ <!-- Direction Selector -->
930
+ <div class="col-fixed">
931
+ <p-select
932
+ [options]="directionOptions"
933
+ [(ngModel)]="sort.direction"
934
+ (onChange)="updateValue()"
935
+ [placeholder]="'DIRECTION' | translate"
936
+ appendTo="body"
937
+ styleClass="w-full">
938
+ </p-select>
939
+ </div>
940
+ <!-- Actions -->
941
+ <div class="col-fixed flex gap-2" style="justify-content: flex-end;">
942
+ <!-- Move Up -->
943
+ @if (!isFirst) {
944
+ <button
945
+ outlined
946
+ style="width: var(--p-button-icon-only-width);"
947
+ pButton
948
+ icon="pi pi-arrow-up"
949
+ type="button"
950
+ class="p-button-sm p-button-success"
951
+ (click)="moveSort(i, i - 1)"
952
+ [pTooltip]="'MOVE_UP' | translate"
953
+ tooltipPosition="top">
954
+ </button>
955
+ }
956
+ <!-- Move Down -->
957
+ @if (!isLast) {
958
+ <button
959
+ outlined
960
+ style="width: var(--p-button-icon-only-width);"
961
+ pButton
962
+ icon="pi pi-arrow-down"
963
+ type="button"
964
+ class="p-button-sm p-button-success"
965
+ (click)="moveSort(i, i + 1)"
966
+ [pTooltip]="'MOVE_DOWN' | translate"
967
+ tooltipPosition="top">
968
+ </button>
969
+ }
970
+ <!-- Remove -->
971
+ <button
972
+ style="width: var(--p-button-icon-only-width);"
973
+ pButton
974
+ icon="pi pi-times"
975
+ type="button"
976
+ class="p-button-sm p-button-danger"
977
+ (click)="removeSort(i)"
978
+ [pTooltip]="'REMOVE_SORT' | translate"
979
+ tooltipPosition="top">
980
+ </button>
981
+ </div>
982
+ </div>
983
+ </div>
984
+ }
985
+ </div>
986
+ }
987
+
988
+ <!-- Empty State -->
989
+ @if (sorts.length === 0) {
990
+ <div class="empty-state text-center p-4 border-round surface-section">
991
+ <i class="pi pi-sort-alt text-4xl text-500 mb-3"></i>
992
+ <p class="text-500 mb-3">{{ 'NO_SORT_RULES' | translate }}</p>
993
+ <p class="text-400 text-sm mb-3">{{ 'DEFAULT_ORDER' | translate }}</p>
994
+ </div>
995
+ }
996
+
997
+ <!-- Actions -->
998
+ <div class="flex gap-2">
999
+ <button
1000
+ pButton
1001
+ icon="pi pi-sort-alt"
1002
+ [label]="'ADD_SORT' | translate"
1003
+ type="button"
1004
+ class="p-button-outlined flex-1"
1005
+ (click)="addSort()">
1006
+ </button>
1007
+
1008
+ @if (sorts.length > 1) {
1009
+ <button
1010
+ pButton
1011
+ icon="pi pi-random"
1012
+ [label]="'REVERSE_ORDER' | translate"
1013
+ type="button"
1014
+ class="p-button-outlined"
1015
+ (click)="reverseSorts()"
1016
+ [pTooltip]="'REVERSE_TOOLTIP' | translate"
1017
+ tooltipPosition="top">
1018
+ </button>
1019
+ }
1020
+
1021
+ @if (sorts.length > 0) {
1022
+ <button
1023
+ pButton
1024
+ icon="pi pi-trash"
1025
+ type="button"
1026
+ class="p-button-outlined p-button-danger"
1027
+ (click)="clearAll()"
1028
+ [pTooltip]="'CLEAR_ALL_TOOLTIP' | translate"
1029
+ tooltipPosition="top">
1030
+ </button>
1031
+ }
1032
+ </div>
1033
+
1034
+ <!-- Summary -->
1035
+ @if (sorts.length > 0) {
1036
+ <div class="sort-summary mt-3 p-2 border-round surface-ground">
1037
+ <h5 class="mt-0 mb-2 text-sm">{{ 'SORT_SUMMARY' | translate }}:</h5>
1038
+ <div class="text-sm text-500">
1039
+ @for (sort of sorts; track sort; let i = $index) {
1040
+ <span class="sort-step">
1041
+ <span class="font-semibold">{{ getFieldLabel(sort.field) }}</span>
1042
+ <span class="direction-badge" [ngClass]="sort.direction === 'asc' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'">
1043
+ {{ sort.direction === 'asc' ? '▲' : '▼' }}
1044
+ </span>
1045
+ @if (!isLast) {
1046
+ <span> {{ 'THEN' | translate }} </span>
1047
+ }
1048
+ </span>
1049
+ }
1050
+ </div>
1051
+ </div>
1052
+ }
1053
+ </div>`, isInline: true, styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$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: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { 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", "ptTooltip"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1054
+ }
1055
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: SortBuilderComponent, decorators: [{
1056
+ type: Component,
1057
+ args: [{ selector: 'formly-sort-builder', standalone: true, imports: [
1058
+ CommonModule,
1059
+ FormsModule,
1060
+ SelectModule,
1061
+ ButtonModule,
1062
+ DragDropModule,
1063
+ TooltipModule,
1064
+ TranslatePipe
1065
+ ], template: `<div class="sort-builder p-3 border-round border-1 surface-border">
1066
+ <div class="mb-6">
1067
+ <h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || ('SORTING' | translate) }}</h5>
1068
+ <p class="text-gray-600 text-sm">{{ ('PRIORITY_HINT' | translate) }}</p>
1069
+ </div>
1070
+
1071
+ <!-- Sort List -->
1072
+ @if (sorts.length > 0) {
1073
+ <div class="sort-list space-y-2 mb-3">
1074
+ @for (sort of sorts; track sort; let i = $index; let isFirst = $first; let isLast = $last) {
1075
+ <div
1076
+ class="sort-row p-2 border-round surface-card"
1077
+ pDraggable
1078
+ pDroppable
1079
+ (onDragStart)="onDragStart(i)"
1080
+ (onDrop)="onDrop($event, i)"
1081
+ [ngClass]="{'border-left-3 border-primary': isFirst, 'border-left-3 border-200': !isFirst}">
1082
+ <div class="grid grid-cols-4 align-items-center gap-2">
1083
+ <!-- Drag Handle -->
1084
+ <div class="col-span-2 flex">
1085
+ <button
1086
+ pButton
1087
+ icon="pi pi-bars"
1088
+ type="button"
1089
+ class="p-button-text p-button-plain cursor-move"
1090
+ [pTooltip]="'DRAG_TOOLTIP' | translate"
1091
+ tooltipPosition="top">
1092
+ </button>
1093
+ <p-select
1094
+ [options]="getAvailableFields(sort.field)"
1095
+ optionLabel="label"
1096
+ optionValue="key"
1097
+ [(ngModel)]="sort.field"
1098
+ (onChange)="updateValue()"
1099
+ [placeholder]="'SELECT_FIELD_SORT' | translate"
1100
+ [showClear]="true"
1101
+ appendTo="body"
1102
+ styleClass="w-full">
1103
+ </p-select>
1104
+ </div>
1105
+ <!-- Direction Selector -->
1106
+ <div class="col-fixed">
1107
+ <p-select
1108
+ [options]="directionOptions"
1109
+ [(ngModel)]="sort.direction"
1110
+ (onChange)="updateValue()"
1111
+ [placeholder]="'DIRECTION' | translate"
1112
+ appendTo="body"
1113
+ styleClass="w-full">
1114
+ </p-select>
1115
+ </div>
1116
+ <!-- Actions -->
1117
+ <div class="col-fixed flex gap-2" style="justify-content: flex-end;">
1118
+ <!-- Move Up -->
1119
+ @if (!isFirst) {
1120
+ <button
1121
+ outlined
1122
+ style="width: var(--p-button-icon-only-width);"
1123
+ pButton
1124
+ icon="pi pi-arrow-up"
1125
+ type="button"
1126
+ class="p-button-sm p-button-success"
1127
+ (click)="moveSort(i, i - 1)"
1128
+ [pTooltip]="'MOVE_UP' | translate"
1129
+ tooltipPosition="top">
1130
+ </button>
1131
+ }
1132
+ <!-- Move Down -->
1133
+ @if (!isLast) {
1134
+ <button
1135
+ outlined
1136
+ style="width: var(--p-button-icon-only-width);"
1137
+ pButton
1138
+ icon="pi pi-arrow-down"
1139
+ type="button"
1140
+ class="p-button-sm p-button-success"
1141
+ (click)="moveSort(i, i + 1)"
1142
+ [pTooltip]="'MOVE_DOWN' | translate"
1143
+ tooltipPosition="top">
1144
+ </button>
1145
+ }
1146
+ <!-- Remove -->
1147
+ <button
1148
+ style="width: var(--p-button-icon-only-width);"
1149
+ pButton
1150
+ icon="pi pi-times"
1151
+ type="button"
1152
+ class="p-button-sm p-button-danger"
1153
+ (click)="removeSort(i)"
1154
+ [pTooltip]="'REMOVE_SORT' | translate"
1155
+ tooltipPosition="top">
1156
+ </button>
1157
+ </div>
1158
+ </div>
1159
+ </div>
1160
+ }
1161
+ </div>
1162
+ }
1163
+
1164
+ <!-- Empty State -->
1165
+ @if (sorts.length === 0) {
1166
+ <div class="empty-state text-center p-4 border-round surface-section">
1167
+ <i class="pi pi-sort-alt text-4xl text-500 mb-3"></i>
1168
+ <p class="text-500 mb-3">{{ 'NO_SORT_RULES' | translate }}</p>
1169
+ <p class="text-400 text-sm mb-3">{{ 'DEFAULT_ORDER' | translate }}</p>
1170
+ </div>
1171
+ }
1172
+
1173
+ <!-- Actions -->
1174
+ <div class="flex gap-2">
1175
+ <button
1176
+ pButton
1177
+ icon="pi pi-sort-alt"
1178
+ [label]="'ADD_SORT' | translate"
1179
+ type="button"
1180
+ class="p-button-outlined flex-1"
1181
+ (click)="addSort()">
1182
+ </button>
1183
+
1184
+ @if (sorts.length > 1) {
1185
+ <button
1186
+ pButton
1187
+ icon="pi pi-random"
1188
+ [label]="'REVERSE_ORDER' | translate"
1189
+ type="button"
1190
+ class="p-button-outlined"
1191
+ (click)="reverseSorts()"
1192
+ [pTooltip]="'REVERSE_TOOLTIP' | translate"
1193
+ tooltipPosition="top">
1194
+ </button>
1195
+ }
1196
+
1197
+ @if (sorts.length > 0) {
1198
+ <button
1199
+ pButton
1200
+ icon="pi pi-trash"
1201
+ type="button"
1202
+ class="p-button-outlined p-button-danger"
1203
+ (click)="clearAll()"
1204
+ [pTooltip]="'CLEAR_ALL_TOOLTIP' | translate"
1205
+ tooltipPosition="top">
1206
+ </button>
1207
+ }
1208
+ </div>
1209
+
1210
+ <!-- Summary -->
1211
+ @if (sorts.length > 0) {
1212
+ <div class="sort-summary mt-3 p-2 border-round surface-ground">
1213
+ <h5 class="mt-0 mb-2 text-sm">{{ 'SORT_SUMMARY' | translate }}:</h5>
1214
+ <div class="text-sm text-500">
1215
+ @for (sort of sorts; track sort; let i = $index) {
1216
+ <span class="sort-step">
1217
+ <span class="font-semibold">{{ getFieldLabel(sort.field) }}</span>
1218
+ <span class="direction-badge" [ngClass]="sort.direction === 'asc' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'">
1219
+ {{ sort.direction === 'asc' ? '▲' : '▼' }}
1220
+ </span>
1221
+ @if (!isLast) {
1222
+ <span> {{ 'THEN' | translate }} </span>
1223
+ }
1224
+ </span>
1225
+ }
1226
+ </div>
1227
+ </div>
1228
+ }
1229
+ </div>` }]
1230
+ }] });
1231
+
1232
+ // query-builder.component.ts
1233
+ class QueryBuilderComponent extends FieldType {
1234
+ queryBuilderService = inject(QueryBuilderService);
1235
+ cdr = inject(ChangeDetectorRef);
1236
+ translate = inject(LocalizationService);
1237
+ groups = [];
1238
+ logicalOperators = [
1239
+ { label: this.translate.instant('and'), value: 'and' },
1240
+ { label: this.translate.instant('or'), value: 'or' }
1241
+ ];
1242
+ showAppliedFilters = false;
1243
+ ngOnInit() {
1244
+ // Initialize with existing value or default group with conditions
1245
+ if (this.formControl.value && (Array.isArray(this.formControl.value) && this.formControl.value.length > 0)) {
1246
+ this.initializeAppliedConditionsWithFieldConfigs();
1247
+ this.initializeDefaultConditions();
1248
+ }
1249
+ else {
1250
+ // Start with default conditions for important fields
1251
+ this.initializeDefaultConditions();
1252
+ }
1253
+ }
1254
+ initializeAppliedConditionsWithFieldConfigs() {
1255
+ this.groups = this.formControl.value.map((group, groupIndex) => ({
1256
+ logicalOperator: group.logicalOperator || 'and',
1257
+ groupLogicalOperator: group.groupLogicalOperator || 'and',
1258
+ conditions: this.initializeConditionsWithFieldConfigs(group.conditions || [], groupIndex)
1259
+ }));
1260
+ }
1261
+ initializeConditionsWithFieldConfigs(conditions, groupIndex) {
1262
+ return conditions.map((condition, conditionIndex) => ({
1263
+ ...condition,
1264
+ operatorsForField: this.getOperatorsForField(condition.field),
1265
+ valueFieldConfig: condition.field ?
1266
+ this.generateValueFieldConfig(condition, groupIndex, conditionIndex) :
1267
+ null
1268
+ }));
1269
+ }
1270
+ initializeDefaultConditions() {
1271
+ // Get default fields using the service logic
1272
+ const defaultFields = this.getDefaultFields();
1273
+ const defaultConditions = [];
1274
+ for (let i = 0; i < defaultFields.length; i++) {
1275
+ const field = defaultFields[i];
1276
+ defaultConditions.push(this.createDefaultCondition(field, 0, i, field.props?.operator));
1277
+ }
1278
+ // this.groups = [{
1279
+ // logicalOperator: 'and',
1280
+ // groupLogicalOperator: 'and',
1281
+ // conditions: defaultConditions
1282
+ // }];
1283
+ this.groups = [{
1284
+ logicalOperator: 'and',
1285
+ groupLogicalOperator: 'and',
1286
+ conditions: defaultConditions.filter(dc => !this.groups?.some(g => g.conditions?.some((c) => c.field === dc.field &&
1287
+ c.operator === dc.operator)))
1288
+ }];
1289
+ this.updateValue();
1290
+ }
1291
+ toggleAppliedFilters() {
1292
+ if (this.showAppliedFilters == true) {
1293
+ this.initializeAppliedConditionsWithFieldConfigs();
1294
+ this.initializeDefaultConditions();
1295
+ }
1296
+ else {
1297
+ this.initializeAppliedConditionsWithFieldConfigs();
1298
+ }
1299
+ this.showAppliedFilters = !this.showAppliedFilters;
1300
+ }
1301
+ onAndClick(group, event) {
1302
+ group.logicalOperator = 'and';
1303
+ this.updateValue();
1304
+ }
1305
+ onOrClick(group, event) {
1306
+ group.logicalOperator = 'or';
1307
+ this.updateValue();
1308
+ }
1309
+ getLogicalOperatorText(operator) {
1310
+ const operatorMap = {
1311
+ 'and': this.translate.instant('and'),
1312
+ 'or': this.translate.instant('or'),
1313
+ 'not': this.translate.instant('not')
1314
+ };
1315
+ return operatorMap[operator] || operator;
1316
+ }
1317
+ getDefaultFields() {
1318
+ // Get all filterable fields
1319
+ const filterableFields = this.props['fields'].filter((field) => field.filterable !== false && field.key && this.isSupportedFieldType(field.type));
1320
+ // If no filterable fields, return empty array
1321
+ if (filterableFields.length === 0) {
1322
+ return [];
1323
+ }
1324
+ const selectedFields = filterableFields;
1325
+ return selectedFields;
1326
+ }
1327
+ isSupportedFieldType(fieldType) {
1328
+ // Only include field types that make sense for filtering
1329
+ const supportedTypes = ['input', 'number', 'datepicker', 'custom-datepicker', 'select', 'checkbox', 'generic-selector', 'switch', 'radio', 'textarea'];
1330
+ return supportedTypes.includes(fieldType);
1331
+ }
1332
+ getFieldLabel(key) {
1333
+ const f = this.props['fields']?.find((x) => x.key === key);
1334
+ return f ? f.label : '';
1335
+ }
1336
+ createDefaultCondition(field, groupIndex, conditionIndex, operator) {
1337
+ const defaultCondition = {
1338
+ field: field.key,
1339
+ operator: operator ? operator : this.getDefaultOperator(field.type),
1340
+ value: this.getDefaultValue(field.type),
1341
+ valueFieldConfig: null,
1342
+ operatorsForField: this.getOperatorsForField(field),
1343
+ };
1344
+ // Generate field config for this condition
1345
+ defaultCondition.valueFieldConfig = this.generateValueFieldConfig(defaultCondition, groupIndex, conditionIndex);
1346
+ return defaultCondition;
1347
+ }
1348
+ get filterCount() {
1349
+ const filters = ((this.model?.advancedFilters ?? [])[0]?.conditions?.length || 0) +
1350
+ ((this.model?.sorting ?? [])?.length || 0) +
1351
+ (this.model?.pagination && this.model?.pagination?.top ? 1 : 0);
1352
+ return filters;
1353
+ }
1354
+ // Field type and operator methods that use QueryBuilderService logic
1355
+ getFieldType(fieldKey) {
1356
+ const field = this.props['fields'].find((f) => f.key === fieldKey);
1357
+ return field?.type || 'input';
1358
+ }
1359
+ getOperatorsForField(field) {
1360
+ if (!field)
1361
+ return [];
1362
+ let fieldKey;
1363
+ if (typeof field === 'object' && field !== null) {
1364
+ fieldKey = field['key'];
1365
+ }
1366
+ else {
1367
+ fieldKey = field;
1368
+ }
1369
+ const field_ = this.props['fields'].find((f) => f.key === fieldKey);
1370
+ // Use operators from the field configuration
1371
+ const operators = field_?.operators || this.queryBuilderService['getOperatorsForType'](field_?.type);
1372
+ return operators.map((op) => ({
1373
+ label: this.getOperatorLabel(op).label,
1374
+ icon: this.getOperatorLabel(op).icon,
1375
+ value: op
1376
+ }));
1377
+ }
1378
+ getOperatorLabel(operator) {
1379
+ const map = {
1380
+ eq: { label: this.translate.instant('eq'), icon: 'pi pi-equals' },
1381
+ ne: { label: this.translate.instant('ne'), icon: 'pi pi-hashtag' },
1382
+ contains: { label: this.translate.instant('contains'), icon: 'pi pi-search' },
1383
+ startswith: { label: this.translate.instant('startswith'), icon: 'pi pi-arrow-right' },
1384
+ endswith: { label: this.translate.instant('endswith'), icon: 'pi pi-arrow-left' },
1385
+ gt: { label: this.translate.instant('gt'), icon: 'pi pi-chevron-right' },
1386
+ ge: { label: this.translate.instant('ge'), icon: 'pi pi-angle-double-right' },
1387
+ lt: { label: this.translate.instant('lt'), icon: 'pi pi-chevron-left' },
1388
+ le: { label: this.translate.instant('le'), icon: 'pi pi-angle-double-left' },
1389
+ in: { label: this.translate.instant('in'), icon: 'pi pi-list' },
1390
+ notin: { label: this.translate.instant('notin'), icon: 'pi pi-ban' },
1391
+ };
1392
+ return map[operator] || { label: operator, icon: 'pi pi-question' };
1393
+ }
1394
+ getDefaultOperator(fieldType) {
1395
+ // Use service's field type config
1396
+ const fieldConfig = this.queryBuilderService['fieldTypeConfig'][fieldType];
1397
+ return fieldConfig ? fieldConfig.defaultOperator : 'contains';
1398
+ }
1399
+ getDefaultValue(fieldType) {
1400
+ // Use service's field type config
1401
+ const fieldConfig = this.queryBuilderService['fieldTypeConfig'][fieldType];
1402
+ return fieldConfig ? fieldConfig.defaultValue : '';
1403
+ }
1404
+ getFieldProps(fieldKey) {
1405
+ const field = this.props['fields'].find((f) => f.key === fieldKey);
1406
+ // Return props without label to avoid duplication
1407
+ const props = { ...field?.props };
1408
+ delete props.label;
1409
+ delete props.loadDefault;
1410
+ return props;
1411
+ }
1412
+ generateValueFieldConfig(condition, groupIndex, conditionIndex) {
1413
+ const fieldType = this.getFieldType(condition.field);
1414
+ const fieldProps = this.getFieldProps(condition.field);
1415
+ const fieldKey = this.getConditionValueKey(groupIndex, conditionIndex);
1416
+ return {
1417
+ key: fieldKey,
1418
+ type: fieldType,
1419
+ wrappers: [],
1420
+ props: {
1421
+ ...fieldProps, // Include all other props from the original field
1422
+ placeholder: '',
1423
+ loadDefault: false,
1424
+ change: (field, event) => {
1425
+ this.updateValue();
1426
+ }
1427
+ },
1428
+ hooks: {
1429
+ onInit: (field) => {
1430
+ if (field.formControl && condition.value !== undefined) {
1431
+ field.formControl.setValue(condition.value, { emitEvent: false });
1432
+ }
1433
+ }
1434
+ },
1435
+ modelOptions: {
1436
+ updateOn: 'change'
1437
+ }
1438
+ };
1439
+ }
1440
+ getConditionValueKey(groupIndex, conditionIndex) {
1441
+ return `condition_${groupIndex}_${conditionIndex}_value`;
1442
+ }
1443
+ // Group Management
1444
+ addGroup() {
1445
+ this.groups.push({
1446
+ logicalOperator: 'and',
1447
+ groupLogicalOperator: 'and',
1448
+ conditions: []
1449
+ });
1450
+ this.updateValue();
1451
+ }
1452
+ // removeGroup(groupIndex: number) {
1453
+ // this.groups.splice(groupIndex, 1);
1454
+ // this.updateValue();
1455
+ // }
1456
+ removeGroup(groupIndex) {
1457
+ if (this.groups[groupIndex]) {
1458
+ // Get all field keys for this group before deleting
1459
+ const fieldKeysToRemove = [];
1460
+ // Collect all condition field keys in this group
1461
+ if (this.groups[groupIndex].conditions) {
1462
+ for (let c = 0; c < this.groups[groupIndex].conditions.length; c++) {
1463
+ const fieldKey = this.getConditionValueKey(groupIndex, c);
1464
+ fieldKeysToRemove.push(fieldKey);
1465
+ }
1466
+ }
1467
+ // Remove the group
1468
+ this.groups.splice(groupIndex, 1);
1469
+ // Remove all associated fields from model
1470
+ this.removeMultipleFromModel(fieldKeysToRemove);
1471
+ this.updateValue();
1472
+ }
1473
+ }
1474
+ // Remove multiple fields from formly model
1475
+ removeMultipleFromModel(fieldKeys) {
1476
+ fieldKeys.forEach(fieldKey => {
1477
+ this.removeFromModel(fieldKey);
1478
+ });
1479
+ }
1480
+ // Condition Management
1481
+ addCondition(groupIndex = 0) {
1482
+ if (!this.groups[groupIndex]) {
1483
+ this.addGroup();
1484
+ }
1485
+ const newCondition = {
1486
+ field: '',
1487
+ operator: 'eq',
1488
+ value: '',
1489
+ valueFieldConfig: {}
1490
+ };
1491
+ const conditionIndex = this.groups[groupIndex].conditions.length;
1492
+ newCondition.valueFieldConfig = this.generateValueFieldConfig(newCondition, groupIndex, conditionIndex);
1493
+ this.groups[groupIndex].conditions.push(newCondition);
1494
+ this.updateValue();
1495
+ }
1496
+ onFieldChange(condition, groupIndex, conditionIndex) {
1497
+ condition.operator = this.getDefaultOperator(this.getFieldType(condition.field));
1498
+ condition.value = this.getDefaultValue(this.getFieldType(condition.field));
1499
+ // Generate and store the field config when field changes
1500
+ condition.valueFieldConfig = this.generateValueFieldConfig(condition, groupIndex, conditionIndex);
1501
+ condition.operatorsForField = this.getOperatorsForField(condition.field);
1502
+ this.updateValue();
1503
+ }
1504
+ addConditionToLastGroup() {
1505
+ const lastGroupIndex = this.groups.length - 1;
1506
+ if (lastGroupIndex >= 0) {
1507
+ this.addCondition(lastGroupIndex);
1508
+ }
1509
+ else {
1510
+ this.addGroup();
1511
+ this.addCondition(0);
1512
+ }
1513
+ }
1514
+ removeCondition(groupIndex, conditionIndex) {
1515
+ if (this.groups[groupIndex]) {
1516
+ // Get the field key before deleting so we can remove from model
1517
+ const fieldKey = this.getConditionValueKey(groupIndex, conditionIndex);
1518
+ // Using delete operator (sets to undefined, then we filter)
1519
+ delete this.groups[groupIndex].conditions[conditionIndex];
1520
+ // Remove the undefined entries from conditions array
1521
+ this.groups[groupIndex].conditions = this.groups[groupIndex].conditions.filter((condition) => condition !== undefined);
1522
+ // Remove empty groups
1523
+ if (this.groups[groupIndex].conditions.length === 0 && this.groups.length > 1) {
1524
+ delete this.groups[groupIndex];
1525
+ // Remove undefined groups from array
1526
+ this.groups = this.groups.filter(group => group !== undefined);
1527
+ }
1528
+ // Remove from formly model
1529
+ this.removeFromModel(fieldKey);
1530
+ this.updateValue();
1531
+ }
1532
+ }
1533
+ // Remove the field from formly model
1534
+ removeFromModel(fieldKey) {
1535
+ if (this.model && this.model.hasOwnProperty(fieldKey)) {
1536
+ delete this.model[fieldKey];
1537
+ }
1538
+ // Also update the form control to ensure sync
1539
+ if (this.formControl.value && this.formControl.value.hasOwnProperty(fieldKey)) {
1540
+ const updatedValue = { ...this.formControl.value };
1541
+ delete updatedValue[fieldKey];
1542
+ this.formControl.setValue(updatedValue);
1543
+ }
1544
+ }
1545
+ // Value Management
1546
+ updateValue() {
1547
+ // Sync condition values from form model with safe access
1548
+ for (let g = 0; g < this.groups.length; g++) {
1549
+ const group = this.groups[g];
1550
+ for (let c = 0; c < group.conditions.length; c++) {
1551
+ const condition = group.conditions[c];
1552
+ const fieldKey = this.getConditionValueKey(g, c);
1553
+ try {
1554
+ const modelValue = this.model?.[fieldKey];
1555
+ if (modelValue !== undefined) {
1556
+ condition.value = modelValue;
1557
+ }
1558
+ }
1559
+ catch (error) {
1560
+ }
1561
+ }
1562
+ }
1563
+ const filteredGroups = [];
1564
+ for (let g = 0; g < this.groups.length; g++) {
1565
+ const group = this.groups[g];
1566
+ const validConditions = [];
1567
+ for (let c = 0; c < group.conditions.length; c++) {
1568
+ const condition = group.conditions[c];
1569
+ if (condition.field &&
1570
+ condition.operator &&
1571
+ condition.value !== undefined &&
1572
+ condition.value !== null &&
1573
+ condition.value !== '') {
1574
+ validConditions.push(condition);
1575
+ }
1576
+ }
1577
+ if (validConditions.length > 0) {
1578
+ filteredGroups.push({ ...group, conditions: validConditions });
1579
+ }
1580
+ }
1581
+ this.formControl.setValue(filteredGroups);
1582
+ this.cdr.detectChanges();
1583
+ }
1584
+ clearAll() {
1585
+ this.groups = [];
1586
+ // Completely reset the form and model
1587
+ this.form.reset();
1588
+ // Manually set model to empty object
1589
+ Object.keys(this.model).forEach(key => {
1590
+ delete this.model[key];
1591
+ });
1592
+ this.addGroup();
1593
+ }
1594
+ // Helper methods
1595
+ getQueryStructure() {
1596
+ return this.groups;
1597
+ }
1598
+ loadQueryStructure(queryStructure) {
1599
+ if (queryStructure && Array.isArray(queryStructure)) {
1600
+ this.groups = queryStructure.map((group, groupIndex) => ({
1601
+ logicalOperator: group.logicalOperator || 'and',
1602
+ groupLogicalOperator: group.groupLogicalOperator || 'and',
1603
+ conditions: this.initializeConditionsWithFieldConfigs(group.conditions || [], groupIndex)
1604
+ }));
1605
+ this.updateValue();
1606
+ }
1607
+ }
1608
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: QueryBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1609
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: QueryBuilderComponent, isStandalone: true, selector: "formly-query-builder", usesInheritance: true, ngImport: i0, template: "<div>\n\n <!-- Groups Container with Connecting Lines -->\n <div class=\"groups-container\" #groupsContainer>\n @for (group of groups; track group; let groupIndex = $index) {\n <div\n class=\"group-wrapper relative\"\n #groupElement>\n <!-- Vertical Connector Line from Previous Group -->\n @if (groupIndex > 0) {\n <div class=\"vertical-connector\">\n <div class=\"vertical-line\"></div>\n <!-- Operator Button -->\n <button\n type=\"button\"\n class=\"group-logical-operator-box\"\n [class.and]=\"group.groupLogicalOperator === 'and'\"\n [class.or]=\"group.groupLogicalOperator === 'or'\"\n (click)=\"opPopover.toggle($event)\">\n <span class=\"operator-text\">\n {{ getLogicalOperatorText(group.groupLogicalOperator || 'and') }}\n </span>\n <div class=\"connector-arrow\"></div>\n </button>\n <!-- Popover -->\n <p-popover #opPopover appendTo=\"body\">\n <div class=\"w-12rem\">\n <p-listbox\n [options]=\"logicalOperators\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [(ngModel)]=\"group.groupLogicalOperator\"\n (onChange)=\" opPopover.toggle($event)\"\n [style]=\"{ width: '100%' }\">\n </p-listbox>\n </div>\n </p-popover>\n </div>\n }\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\"\n [class.border-blue-500]=\"group.logicalOperator === 'and'\"\n [class.border-or-500]=\"group.logicalOperator === 'or'\"\n [class.pulse]=\"groupIndex === 0\">\n <!-- Rounded Button for \"\u0631\u0628\u0637 \u0627\u0644\u0634\u0631\u0648\u0637\" on left border -->\n <div\n class=\"absolute -left-4 top-1/2 transform -translate-y-1/2 z-10 transition-all duration-300\"\n >\n <!-- Logical Operator Popover Button -->\n <p-button\n [severity]=\"group.logicalOperator === 'and' ? 'success' : 'info'\"\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)\">\n {{group.logicalOperator === 'and' ? ('and' | translate) : ('or' | translate)}}\n </p-button>\n <!-- Popover for Operator Selection -->\n <p-popover #opPopover appendTo=\"body\">\n <div class=\"w-10rem\">\n <p-listbox\n [options]=\"[\r\n { label: 'and' | translate, value: 'and' },\r\n { label: 'or' | translate, value: 'or' }\r\n ]\"\r\n optionLabel=\"label\"\n optionValue=\"value\"\n [(ngModel)]=\"group.logicalOperator\"\n (onChange)=\"updateValue(); opPopover.toggle($event)\"\n [style]=\"{ width: '100%' }\">\n <ng-template let-item pTemplate=\"item\">\n {{ item.label }}\n </ng-template>\n </p-listbox>\n </div>\n </p-popover>\n </div>\n <!-- Group Header -->\n <div class=\"group-header-wrapper mb-4 pl-8\">\n <div class=\"group-header flex justify-between items-center\">\n <div class=\"flex gap-1\">\n <button\n pButton\n icon=\"pi pi-plus\"\n type=\"button\"\n class=\"p-button-success p-button p-button-sm !p-1.5 rounded-md transition-colors\"\n (click)=\"addCondition(groupIndex)\"\n pTooltip=\"{{ 'ADD_CONDITION' | translate }}\"\n tooltipPosition=\"top\">\n </button>\n <!-- @if (filterCount > 0) {\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-filter\"\n class=\"p-button-outlined p-button-sm\"\n (click)=\"toggleAppliedFilters()\">\n {{ (showAppliedFilters ? 'SHOW_ALL' : 'SHOW_APPLIED') | translate }}\n </button>\n } -->\n @if (groups.length > 1) {\n <button\n pButton\n icon=\"pi pi-times\"\n type=\"button\"\n class=\"p-button-danger p-button p-button-sm !p-1.5 rounded-md transition-colors\"\n (click)=\"removeGroup(groupIndex)\"\n pTooltip=\"{{ 'DELETE_GROUP' | translate }}\"\n tooltipPosition=\"top\">\n </button>\n }\n </div>\n </div>\n </div>\n <!-- Conditions in this group -->\n @for (condition of group.conditions; track condition; let conditionIndex = $index) {\n <div\n class=\"p-1 bg-gray-50 \">\n <div class=\"grid grid-cols-1 gap-1 items-center\">\n <!-- Field Selector -->\n <div class=\"md:col-span-2\">\n <div class=\"flex w-full rounded-md overflow-hidden border border-gray-300 bg-white shadow-sm\">\n <!-- Field Selector Button -->\n <div style=\"align-items: center;display: flex; min-width: 130px;max-width: 130px;\" class=\"flex-shrink-0 \">\n <button\n type=\"button\"\n style=\"height: -webkit-fill-available;\"\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\"\n (click)=\"overlay.toggle($event)\">\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>\n </button>\n <!-- Overlay Content -->\n <p-popover #overlay appendTo=\"body\">\n <div class=\"w-60\">\n <p-listbox\n tabindex=\"0\"\n [options]=\"props['fields']\"\n optionLabel=\"label\"\n optionValue=\"key\"\n [(ngModel)]=\"condition.field\"\n (onChange)=\"onFieldChange(condition, groupIndex, conditionIndex); overlay.toggle($event)\"\n [style]=\"{ width: '100%' }\">\n </p-listbox>\n </div>\n </p-popover>\n </div>\n <!-- Operator Button -->\n <div style=\"align-items: center;display: flex;\" class=\"flex-shrink-0 border-l border-r border-gray-300\">\n <button\n type=\"button\"\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\"\n style=\"height: -webkit-fill-available;\"\n pTooltip=\"{{ getOperatorLabel(condition.operator).label }}\"\n (click)=\"opPopover.toggle($event)\">\n <i [class]=\"getOperatorLabel(condition.operator).icon\"></i>\n </button>\n <p-popover #opPopover appendTo=\"body\">\n <div class=\"w-60\">\n <p-listbox\n [options]=\"condition.operatorsForField\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [(ngModel)]=\"condition.operator\"\n (onChange)=\"updateValue();opPopover.toggle($event)\"\n [style]=\"{ width: '100%' }\">\n </p-listbox>\n </div>\n </p-popover>\n </div>\n <!-- Form Field -->\n <div style=\"background: #f9fafb\" class=\"flex-grow p-1\">\n <formly-form\n [form]=\"form\"\n [fields]=\"[condition.valueFieldConfig]\"\n [model]=\"model\">\n </formly-form>\n </div>\n <!-- Delete Button -->\n <div style=\"align-items: center;display: flex;\" class=\"flex-shrink-0\">\n <button\n style=\"height: -webkit-fill-available;\"\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\"\n type=\"button\"\n (click)=\"removeCondition(groupIndex, conditionIndex)\"\n pTooltip=\"\u062D\u0630\u0641 \u0627\u0644\u0634\u0631\u0637\"\n tooltipPosition=\"top\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- Group Footer -->\n <div class=\"group-footer flex justify-between items-center mt-3 pt-3 border-t border-gray-200 \">\n <small class=\"text-gray-500\">\n {{ group.conditions.length }} \u0634\u0631\u0637 \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u0645\u062C\u0645\u0648\u0639\u0629\n </small>\n <small class=\"text-gray-500\">\n @if (groupIndex > 0) {\n <span>\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'\"\n [class.text-amber-600]=\"group.groupLogicalOperator === 'or'\">\n {{ group.groupLogicalOperator === 'and' ? '\u0648' : '\u0623\u0648' }}\n </strong>\n </span>\n }\n </small>\n </div>\n </div>\n </div>\n }\n</div>\n\n<!-- Actions -->\n<div class=\"flex flex-wrap gap-3 mt-6\">\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-plus\"\n [label]=\"'GROUP' | translate\"\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\"\n (click)=\"addGroup();\">\n </button>\n\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-plus\"\n [label]=\"'CONDITION' | translate\"\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\"\n (click)=\"addConditionToLastGroup()\">\n </button>\n\n @if (groups.length > 0) {\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-trash\"\n [label]=\"'CLEAR' | translate\"\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\"\n (click)=\"clearAll()\">\n </button>\n }\n</div>\n\n<!-- Empty State -->\n@if (groups.length === 0) {\n <div class=\"empty-state text-center p-6 border-2 border-dashed border-gray-300 rounded-xl bg-gray-50\">\n <i class=\"pi pi-search text-4xl text-gray-400 mb-3\"></i>\n <p class=\"text-gray-500 mb-4\">{{ 'NO_SEARCH_CONDITIONS' | translate }}</p>\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-plus\"\n [label]=\"'ADD_SEARCH_CONDITION' | translate\"\n class=\"p-button-outlined !px-4 !py-2\"\n (click)=\"addGroup()\">\n </button>\n </div>\n}\n</div>\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: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$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: i1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "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", "ptTooltip"] }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: InputGroupModule }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i5$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: i6$2.Listbox, selector: "p-listbox, p-listBox, p-list-box", inputs: ["hostName", "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", "dropListData", "fluid"], outputs: ["onChange", "onClick", "onDblClick", "onFilter", "onFocus", "onBlur", "onSelectAllChange", "onLazyLoad", "onDrop"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1610
+ }
1611
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: QueryBuilderComponent, decorators: [{
1612
+ type: Component,
1613
+ args: [{ selector: 'formly-query-builder', standalone: true, imports: [
1614
+ FormsModule,
1615
+ ReactiveFormsModule,
1616
+ SelectModule,
1617
+ InputTextModule,
1618
+ ButtonModule,
1619
+ DatePickerModule,
1620
+ CheckboxModule,
1621
+ RadioButtonModule,
1622
+ FormlyForm,
1623
+ TooltipModule,
1624
+ MenuModule,
1625
+ TranslatePipe,
1626
+ InputGroupModule,
1627
+ InputGroupAddonModule,
1628
+ PopoverModule,
1629
+ ListboxModule
1630
+ ], template: "<div>\n\n <!-- Groups Container with Connecting Lines -->\n <div class=\"groups-container\" #groupsContainer>\n @for (group of groups; track group; let groupIndex = $index) {\n <div\n class=\"group-wrapper relative\"\n #groupElement>\n <!-- Vertical Connector Line from Previous Group -->\n @if (groupIndex > 0) {\n <div class=\"vertical-connector\">\n <div class=\"vertical-line\"></div>\n <!-- Operator Button -->\n <button\n type=\"button\"\n class=\"group-logical-operator-box\"\n [class.and]=\"group.groupLogicalOperator === 'and'\"\n [class.or]=\"group.groupLogicalOperator === 'or'\"\n (click)=\"opPopover.toggle($event)\">\n <span class=\"operator-text\">\n {{ getLogicalOperatorText(group.groupLogicalOperator || 'and') }}\n </span>\n <div class=\"connector-arrow\"></div>\n </button>\n <!-- Popover -->\n <p-popover #opPopover appendTo=\"body\">\n <div class=\"w-12rem\">\n <p-listbox\n [options]=\"logicalOperators\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [(ngModel)]=\"group.groupLogicalOperator\"\n (onChange)=\" opPopover.toggle($event)\"\n [style]=\"{ width: '100%' }\">\n </p-listbox>\n </div>\n </p-popover>\n </div>\n }\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\"\n [class.border-blue-500]=\"group.logicalOperator === 'and'\"\n [class.border-or-500]=\"group.logicalOperator === 'or'\"\n [class.pulse]=\"groupIndex === 0\">\n <!-- Rounded Button for \"\u0631\u0628\u0637 \u0627\u0644\u0634\u0631\u0648\u0637\" on left border -->\n <div\n class=\"absolute -left-4 top-1/2 transform -translate-y-1/2 z-10 transition-all duration-300\"\n >\n <!-- Logical Operator Popover Button -->\n <p-button\n [severity]=\"group.logicalOperator === 'and' ? 'success' : 'info'\"\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)\">\n {{group.logicalOperator === 'and' ? ('and' | translate) : ('or' | translate)}}\n </p-button>\n <!-- Popover for Operator Selection -->\n <p-popover #opPopover appendTo=\"body\">\n <div class=\"w-10rem\">\n <p-listbox\n [options]=\"[\r\n { label: 'and' | translate, value: 'and' },\r\n { label: 'or' | translate, value: 'or' }\r\n ]\"\r\n optionLabel=\"label\"\n optionValue=\"value\"\n [(ngModel)]=\"group.logicalOperator\"\n (onChange)=\"updateValue(); opPopover.toggle($event)\"\n [style]=\"{ width: '100%' }\">\n <ng-template let-item pTemplate=\"item\">\n {{ item.label }}\n </ng-template>\n </p-listbox>\n </div>\n </p-popover>\n </div>\n <!-- Group Header -->\n <div class=\"group-header-wrapper mb-4 pl-8\">\n <div class=\"group-header flex justify-between items-center\">\n <div class=\"flex gap-1\">\n <button\n pButton\n icon=\"pi pi-plus\"\n type=\"button\"\n class=\"p-button-success p-button p-button-sm !p-1.5 rounded-md transition-colors\"\n (click)=\"addCondition(groupIndex)\"\n pTooltip=\"{{ 'ADD_CONDITION' | translate }}\"\n tooltipPosition=\"top\">\n </button>\n <!-- @if (filterCount > 0) {\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-filter\"\n class=\"p-button-outlined p-button-sm\"\n (click)=\"toggleAppliedFilters()\">\n {{ (showAppliedFilters ? 'SHOW_ALL' : 'SHOW_APPLIED') | translate }}\n </button>\n } -->\n @if (groups.length > 1) {\n <button\n pButton\n icon=\"pi pi-times\"\n type=\"button\"\n class=\"p-button-danger p-button p-button-sm !p-1.5 rounded-md transition-colors\"\n (click)=\"removeGroup(groupIndex)\"\n pTooltip=\"{{ 'DELETE_GROUP' | translate }}\"\n tooltipPosition=\"top\">\n </button>\n }\n </div>\n </div>\n </div>\n <!-- Conditions in this group -->\n @for (condition of group.conditions; track condition; let conditionIndex = $index) {\n <div\n class=\"p-1 bg-gray-50 \">\n <div class=\"grid grid-cols-1 gap-1 items-center\">\n <!-- Field Selector -->\n <div class=\"md:col-span-2\">\n <div class=\"flex w-full rounded-md overflow-hidden border border-gray-300 bg-white shadow-sm\">\n <!-- Field Selector Button -->\n <div style=\"align-items: center;display: flex; min-width: 130px;max-width: 130px;\" class=\"flex-shrink-0 \">\n <button\n type=\"button\"\n style=\"height: -webkit-fill-available;\"\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\"\n (click)=\"overlay.toggle($event)\">\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>\n </button>\n <!-- Overlay Content -->\n <p-popover #overlay appendTo=\"body\">\n <div class=\"w-60\">\n <p-listbox\n tabindex=\"0\"\n [options]=\"props['fields']\"\n optionLabel=\"label\"\n optionValue=\"key\"\n [(ngModel)]=\"condition.field\"\n (onChange)=\"onFieldChange(condition, groupIndex, conditionIndex); overlay.toggle($event)\"\n [style]=\"{ width: '100%' }\">\n </p-listbox>\n </div>\n </p-popover>\n </div>\n <!-- Operator Button -->\n <div style=\"align-items: center;display: flex;\" class=\"flex-shrink-0 border-l border-r border-gray-300\">\n <button\n type=\"button\"\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\"\n style=\"height: -webkit-fill-available;\"\n pTooltip=\"{{ getOperatorLabel(condition.operator).label }}\"\n (click)=\"opPopover.toggle($event)\">\n <i [class]=\"getOperatorLabel(condition.operator).icon\"></i>\n </button>\n <p-popover #opPopover appendTo=\"body\">\n <div class=\"w-60\">\n <p-listbox\n [options]=\"condition.operatorsForField\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [(ngModel)]=\"condition.operator\"\n (onChange)=\"updateValue();opPopover.toggle($event)\"\n [style]=\"{ width: '100%' }\">\n </p-listbox>\n </div>\n </p-popover>\n </div>\n <!-- Form Field -->\n <div style=\"background: #f9fafb\" class=\"flex-grow p-1\">\n <formly-form\n [form]=\"form\"\n [fields]=\"[condition.valueFieldConfig]\"\n [model]=\"model\">\n </formly-form>\n </div>\n <!-- Delete Button -->\n <div style=\"align-items: center;display: flex;\" class=\"flex-shrink-0\">\n <button\n style=\"height: -webkit-fill-available;\"\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\"\n type=\"button\"\n (click)=\"removeCondition(groupIndex, conditionIndex)\"\n pTooltip=\"\u062D\u0630\u0641 \u0627\u0644\u0634\u0631\u0637\"\n tooltipPosition=\"top\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- Group Footer -->\n <div class=\"group-footer flex justify-between items-center mt-3 pt-3 border-t border-gray-200 \">\n <small class=\"text-gray-500\">\n {{ group.conditions.length }} \u0634\u0631\u0637 \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u0645\u062C\u0645\u0648\u0639\u0629\n </small>\n <small class=\"text-gray-500\">\n @if (groupIndex > 0) {\n <span>\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'\"\n [class.text-amber-600]=\"group.groupLogicalOperator === 'or'\">\n {{ group.groupLogicalOperator === 'and' ? '\u0648' : '\u0623\u0648' }}\n </strong>\n </span>\n }\n </small>\n </div>\n </div>\n </div>\n }\n</div>\n\n<!-- Actions -->\n<div class=\"flex flex-wrap gap-3 mt-6\">\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-plus\"\n [label]=\"'GROUP' | translate\"\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\"\n (click)=\"addGroup();\">\n </button>\n\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-plus\"\n [label]=\"'CONDITION' | translate\"\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\"\n (click)=\"addConditionToLastGroup()\">\n </button>\n\n @if (groups.length > 0) {\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-trash\"\n [label]=\"'CLEAR' | translate\"\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\"\n (click)=\"clearAll()\">\n </button>\n }\n</div>\n\n<!-- Empty State -->\n@if (groups.length === 0) {\n <div class=\"empty-state text-center p-6 border-2 border-dashed border-gray-300 rounded-xl bg-gray-50\">\n <i class=\"pi pi-search text-4xl text-gray-400 mb-3\"></i>\n <p class=\"text-gray-500 mb-4\">{{ 'NO_SEARCH_CONDITIONS' | translate }}</p>\n <button\n pButton\n type=\"button\"\n icon=\"pi pi-plus\"\n [label]=\"'ADD_SEARCH_CONDITION' | translate\"\n class=\"p-button-outlined !px-4 !py-2\"\n (click)=\"addGroup()\">\n </button>\n </div>\n}\n</div>\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"] }]
1631
+ }] });
1632
+
1633
+ // tab-type.component.ts
1634
+ class TabTypeComponent extends FieldType {
1635
+ activeTabValue = '0';
1636
+ ngOnInit() {
1637
+ if (this.props['activeIndex'] !== undefined) {
1638
+ this.activeTabValue = this.props['activeIndex'].toString();
1639
+ }
1640
+ }
1641
+ resolveBadge(tab) {
1642
+ const badge = tab.props?.['badge'];
1643
+ if (typeof badge === 'function') {
1644
+ const result = badge(tab);
1645
+ return result == 0 ? null : result;
1646
+ }
1647
+ return badge ?? null;
1648
+ }
1649
+ onTabChange(newValue) {
1650
+ this.activeTabValue = newValue;
1651
+ if (this.props['onTabChange']) {
1652
+ this.props['onTabChange']({
1653
+ activeIndex: parseInt(newValue),
1654
+ tab: this.field.fieldGroup?.[parseInt(newValue)]
1655
+ });
1656
+ }
1657
+ }
1658
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TabTypeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1659
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: TabTypeComponent, isStandalone: true, selector: "formly-tab-type", usesInheritance: true, ngImport: i0, template: `
1660
+ <p-tabs
1661
+ [value]="activeTabValue"
1662
+ [scrollable]="props['scrollable'] !== false"
1663
+ [lazy]="props['lazy'] !== false"
1664
+ (valueChange)="onTabChange($event)">
1665
+
1666
+ <p-tablist>
1667
+ @for (tab of field.fieldGroup; track tab; let i = $index) {
1668
+ <p-tab
1669
+ [value]="i.toString()"
1670
+ [disabled]="tab.props?.disabled">
1671
+ @if(tab.props){
1672
+ <div class="flex align-items-center gap-2">
1673
+ <!-- Left Icon -->
1674
+ @if (tab.props['leftIcon']) {
1675
+ <i [class]="tab.props['leftIcon']" class="text-sm"></i>
1676
+ }
1677
+ <!-- Label -->
1678
+ <span>{{ (tab.props['tabLabel'] ?? '') | translate }}</span>
1679
+ <!-- Badge -->
1680
+ @if (resolveBadge(tab) !== null) {
1681
+ <p-badge
1682
+ [value]="resolveBadge(tab)"
1683
+ size="small">
1684
+ </p-badge>
1685
+ }
1686
+ <!-- Right Icon -->
1687
+ @if (tab.props['rightIcon']) {
1688
+ <i [class]="tab.props['rightIcon']" class="text-sm"></i>
1689
+ }
1690
+ </div>
1691
+ }
1692
+ </p-tab>
1693
+ }
1694
+ </p-tablist>
1695
+
1696
+ <p-tabpanels style="padding: 0;">
1697
+ @for (tab of field.fieldGroup; track tab; let i = $index) {
1698
+ <p-tabpanel
1699
+ [value]="i.toString()">
1700
+ <div class="p-3">
1701
+ <formly-field [field]="tab"></formly-field>
1702
+ <!-- Empty state -->
1703
+ @if (!tab.fieldGroup || tab.fieldGroup.length === 0) {
1704
+ <div
1705
+ class="empty-tab text-center p-4 text-500">
1706
+ <i class="pi pi-inbox text-2xl mb-2"></i>
1707
+ <p>لا توجد حقول في هذه التبويبة</p>
1708
+ </div>
1709
+ }
1710
+ </div>
1711
+ </p-tabpanel>
1712
+ }
1713
+ </p-tabpanels>
1714
+ </p-tabs>
1715
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: TabsModule }, { kind: "component", type: i1$3.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i1$3.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i1$3.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i1$3.TabList, selector: "p-tablist" }, { kind: "component", type: i1$3.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i8.Badge, selector: "p-badge", inputs: ["styleClass", "badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "component", type: FormlyField, selector: "formly-field", inputs: ["field"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1716
+ }
1717
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: TabTypeComponent, decorators: [{
1718
+ type: Component,
1719
+ args: [{
1720
+ selector: 'formly-tab-type',
1721
+ template: `
1722
+ <p-tabs
1723
+ [value]="activeTabValue"
1724
+ [scrollable]="props['scrollable'] !== false"
1725
+ [lazy]="props['lazy'] !== false"
1726
+ (valueChange)="onTabChange($event)">
1727
+
1728
+ <p-tablist>
1729
+ @for (tab of field.fieldGroup; track tab; let i = $index) {
1730
+ <p-tab
1731
+ [value]="i.toString()"
1732
+ [disabled]="tab.props?.disabled">
1733
+ @if(tab.props){
1734
+ <div class="flex align-items-center gap-2">
1735
+ <!-- Left Icon -->
1736
+ @if (tab.props['leftIcon']) {
1737
+ <i [class]="tab.props['leftIcon']" class="text-sm"></i>
1738
+ }
1739
+ <!-- Label -->
1740
+ <span>{{ (tab.props['tabLabel'] ?? '') | translate }}</span>
1741
+ <!-- Badge -->
1742
+ @if (resolveBadge(tab) !== null) {
1743
+ <p-badge
1744
+ [value]="resolveBadge(tab)"
1745
+ size="small">
1746
+ </p-badge>
1747
+ }
1748
+ <!-- Right Icon -->
1749
+ @if (tab.props['rightIcon']) {
1750
+ <i [class]="tab.props['rightIcon']" class="text-sm"></i>
1751
+ }
1752
+ </div>
1753
+ }
1754
+ </p-tab>
1755
+ }
1756
+ </p-tablist>
1757
+
1758
+ <p-tabpanels style="padding: 0;">
1759
+ @for (tab of field.fieldGroup; track tab; let i = $index) {
1760
+ <p-tabpanel
1761
+ [value]="i.toString()">
1762
+ <div class="p-3">
1763
+ <formly-field [field]="tab"></formly-field>
1764
+ <!-- Empty state -->
1765
+ @if (!tab.fieldGroup || tab.fieldGroup.length === 0) {
1766
+ <div
1767
+ class="empty-tab text-center p-4 text-500">
1768
+ <i class="pi pi-inbox text-2xl mb-2"></i>
1769
+ <p>لا توجد حقول في هذه التبويبة</p>
1770
+ </div>
1771
+ }
1772
+ </div>
1773
+ </p-tabpanel>
1774
+ }
1775
+ </p-tabpanels>
1776
+ </p-tabs>
1777
+ `,
1778
+ standalone: true,
1779
+ imports: [TabsModule, BadgeModule, FormlyField, TranslatePipe]
1780
+ }]
1781
+ }] });
1782
+
1783
+ // columns-builder.component.ts
1784
+ class ColumnsBuilderComponent extends FieldType {
1785
+ dragStartIndex = -1;
1786
+ _columnsForm;
1787
+ aggregateFunctions = [
1788
+ { label: 'Sum', value: 'sum' },
1789
+ { label: 'Average', value: 'avg' },
1790
+ { label: 'Count', value: 'count' },
1791
+ { label: 'Minimum', value: 'min' },
1792
+ { label: 'Maximum', value: 'max' }
1793
+ ];
1794
+ get columns() {
1795
+ return this.props['fields'] || [];
1796
+ }
1797
+ get columnsForm() {
1798
+ return this._columnsForm;
1799
+ }
1800
+ get visibleColumnsCount() {
1801
+ return this.columns.filter(col => this.isColumnVisible(col.key)).length;
1802
+ }
1803
+ get numericColumnsCount() {
1804
+ return this.columns.filter(col => col.isNumeric).length;
1805
+ }
1806
+ get aggregatedColumnsCount() {
1807
+ return this.columns.filter(col => this.hasAggregateFunction(col.key)).length;
1808
+ }
1809
+ ngOnInit() {
1810
+ this.initializeForm();
1811
+ this.setupValueChanges();
1812
+ }
1813
+ // Form Methods
1814
+ initializeForm() {
1815
+ const parentForm = this.form;
1816
+ const fieldKey = this.field.key;
1817
+ if (!fieldKey)
1818
+ return;
1819
+ // Get or create the FormGroup in the parent form
1820
+ let formGroup = parentForm.get(fieldKey);
1821
+ if (!formGroup || !(formGroup instanceof FormGroup)) {
1822
+ // Create new FormGroup
1823
+ formGroup = new FormGroup({});
1824
+ parentForm.setControl(fieldKey, formGroup);
1825
+ // Initialize with existing model data
1826
+ const initialModel = this.model[fieldKey] || {};
1827
+ this.columns.forEach(column => {
1828
+ const columnConfig = initialModel[column.key] || {};
1829
+ const columnGroup = new FormGroup({
1830
+ isVisible: new FormControl(columnConfig.isVisible ?? column.defaultVisible),
1831
+ displayName: new FormControl(columnConfig.displayName ?? column.label),
1832
+ aggregateFunction: new FormControl(columnConfig.aggregateFunction ?? ''),
1833
+ order: new FormControl(columnConfig.order ?? 0)
1834
+ });
1835
+ formGroup.addControl(column.key, columnGroup);
1836
+ });
1837
+ }
1838
+ // Store the FormGroup reference
1839
+ this._columnsForm = formGroup;
1840
+ // Initialize the model with current form values
1841
+ this.updateModel();
1842
+ }
1843
+ setupValueChanges() {
1844
+ // Listen to form changes and update the model
1845
+ this.columnsForm.valueChanges.subscribe(() => {
1846
+ this.updateModel();
1847
+ });
1848
+ }
1849
+ updateModel() {
1850
+ const fieldKey = this.field.key;
1851
+ if (fieldKey) {
1852
+ // Update the model with current form values
1853
+ this.model[fieldKey] = this.columnsForm.value;
1854
+ // Trigger form control update to notify Formly
1855
+ this.formControl.setValue(this.columnsForm.value);
1856
+ }
1857
+ }
1858
+ // Called when form controls change
1859
+ onFormChange() {
1860
+ this.updateModel();
1861
+ }
1862
+ // Drag & Drop Methods
1863
+ onDragStart(event, index) {
1864
+ this.dragStartIndex = index;
1865
+ event.dataTransfer?.setData('text/plain', index.toString());
1866
+ }
1867
+ onDragOver(event) {
1868
+ event.preventDefault();
1869
+ }
1870
+ onDrop(event, dropIndex) {
1871
+ event.preventDefault();
1872
+ const dragIndex = this.dragStartIndex;
1873
+ if (dragIndex !== -1 && dragIndex !== dropIndex) {
1874
+ // Reorder the columns array
1875
+ const movedItem = this.columns[dragIndex];
1876
+ const newColumns = [...this.columns];
1877
+ newColumns.splice(dragIndex, 1);
1878
+ newColumns.splice(dropIndex, 0, movedItem);
1879
+ // Update the props to trigger change detection
1880
+ this.props['fields'] = newColumns;
1881
+ // Update order in form controls
1882
+ this.updateColumnOrders();
1883
+ // Update model after reordering
1884
+ this.updateModel();
1885
+ }
1886
+ this.removeDragStyles(event);
1887
+ }
1888
+ onDragEnter(event) {
1889
+ event.currentTarget.classList.add('border-blue-500', 'bg-blue-50');
1890
+ }
1891
+ onDragLeave(event) {
1892
+ this.removeDragStyles(event);
1893
+ }
1894
+ removeDragStyles(event) {
1895
+ event.currentTarget.classList.remove('border-blue-500', 'bg-blue-50');
1896
+ }
1897
+ updateColumnOrders() {
1898
+ // Store the order in form controls
1899
+ this.columns.forEach((column, index) => {
1900
+ const orderControl = this.getColumnControl(column.key, 'order');
1901
+ if (!orderControl) {
1902
+ const columnGroup = this.columnsForm.get(column.key);
1903
+ if (columnGroup && !columnGroup.get('order')) {
1904
+ columnGroup.addControl('order', new FormControl(index));
1905
+ }
1906
+ }
1907
+ else {
1908
+ orderControl.setValue(index);
1909
+ }
1910
+ });
1911
+ }
1912
+ getColumnControl(columnKey, controlName) {
1913
+ const columnGroup = this.columnsForm.get(columnKey);
1914
+ if (!columnGroup) {
1915
+ const fallbackControl = new FormControl(this.getDefaultValue(controlName));
1916
+ return fallbackControl;
1917
+ }
1918
+ const control = columnGroup.get(controlName);
1919
+ if (!control) {
1920
+ const fallbackControl = new FormControl(this.getDefaultValue(controlName));
1921
+ columnGroup.addControl(controlName, fallbackControl);
1922
+ return fallbackControl;
1923
+ }
1924
+ return control;
1925
+ }
1926
+ getDefaultValue(controlName) {
1927
+ switch (controlName) {
1928
+ case 'isVisible': return true;
1929
+ case 'displayName': return '';
1930
+ case 'aggregateFunction': return '';
1931
+ case 'order': return 0;
1932
+ default: return null;
1933
+ }
1934
+ }
1935
+ isColumnVisible(columnKey) {
1936
+ const control = this.getColumnControl(columnKey, 'isVisible');
1937
+ return control?.value === true;
1938
+ }
1939
+ hasAggregateFunction(columnKey) {
1940
+ const control = this.getColumnControl(columnKey, 'aggregateFunction');
1941
+ return !!control?.value;
1942
+ }
1943
+ getAggregateFunctionValue(columnKey) {
1944
+ const control = this.getColumnControl(columnKey, 'aggregateFunction');
1945
+ return control?.value || '';
1946
+ }
1947
+ toggleAll(visible) {
1948
+ this.columns.forEach(column => {
1949
+ const visibleControl = this.getColumnControl(column.key, 'isVisible');
1950
+ if (visibleControl) {
1951
+ visibleControl.setValue(visible);
1952
+ }
1953
+ });
1954
+ this.updateModel();
1955
+ }
1956
+ resetToDefault() {
1957
+ this.columns.forEach((column, index) => {
1958
+ const columnGroup = this.columnsForm.get(column.key);
1959
+ if (columnGroup) {
1960
+ columnGroup.get('isVisible')?.setValue(column.defaultVisible);
1961
+ columnGroup.get('displayName')?.setValue(column.label);
1962
+ columnGroup.get('aggregateFunction')?.setValue('');
1963
+ columnGroup.get('order')?.setValue(index);
1964
+ }
1965
+ });
1966
+ this.updateModel();
1967
+ }
1968
+ getAggregateLabel(functionName) {
1969
+ const func = this.aggregateFunctions.find(f => f.value === functionName);
1970
+ return func ? func.label : functionName;
1971
+ }
1972
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ColumnsBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1973
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ColumnsBuilderComponent, isStandalone: true, selector: "formly-columns-builder", usesInheritance: true, ngImport: i0, template: `
1974
+ <div>
1975
+ <!-- Header -->
1976
+ <div class="mb-6">
1977
+ <h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || ('COLUMNS_MANAGEMENT' | translate) }}</h5>
1978
+ <p class="text-gray-600 text-sm">{{ props.description || ('COLUMNS_DESCRIPTION' | translate) }}</p>
1979
+ </div>
1980
+
1981
+ <!-- Quick Actions -->
1982
+ <div class="flex flex-wrap gap-2 mb-6">
1983
+ <button
1984
+ type="button"
1985
+ (click)="toggleAll(true)"
1986
+ class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
1987
+ >
1988
+ <i class="pi pi-eye"></i>
1989
+ {{ 'SHOW_ALL' | translate }}
1990
+ </button>
1991
+ <button
1992
+ type="button"
1993
+ (click)="toggleAll(false)"
1994
+ class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
1995
+ >
1996
+ <i class="pi pi-eye-slash"></i>
1997
+ {{ 'HIDE_ALL' | translate }}
1998
+ </button>
1999
+ <button
2000
+ type="button"
2001
+ (click)="resetToDefault()"
2002
+ class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
2003
+ >
2004
+ <i class="pi pi-refresh"></i>
2005
+ {{ 'RESET_TO_DEFAULT' | translate }}
2006
+ </button>
2007
+ </div>
2008
+
2009
+ <!-- Drag Info -->
2010
+ <div class="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
2011
+ <i class="pi pi-info-circle mr-2"></i>
2012
+ {{ 'DRAG_COLUMNS_INFO' | translate }}
2013
+ </div>
2014
+
2015
+ <!-- Columns List -->
2016
+ <div class="space-y-3">
2017
+ @if (columns.length === 0) {
2018
+ <div class="text-center py-8 px-4 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50">
2019
+ <i class="pi pi-table text-4xl text-gray-400 mb-3"></i>
2020
+ <p class="text-gray-500">{{ 'NO_COLUMNS_AVAILABLE' | translate }}</p>
2021
+ </div>
2022
+ }
2023
+
2024
+ @for (column of columns; track column; let i = $index) {
2025
+ <div
2026
+ class="bg-gray-50 rounded-lg border border-gray-200 p-1 transition-colors hover:bg-gray-100"
2027
+ draggable="true"
2028
+ (dragstart)="onDragStart($event, i)"
2029
+ (dragover)="onDragOver($event)"
2030
+ (drop)="onDrop($event, i)"
2031
+ (dragenter)="onDragEnter($event)"
2032
+ (dragleave)="onDragLeave($event)">
2033
+ <div class="grid grid-cols-12 gap-4 items-center">
2034
+ <!-- Drag Handle -->
2035
+ <div class="col-span-1 flex justify-center cursor-move text-gray-400 hover:text-gray-600"
2036
+ draggable="true"
2037
+ (dragstart)="onDragStart($event, i)">
2038
+ <i class="pi pi-bars"></i>
2039
+ </div>
2040
+ <!-- Visibility Toggle -->
2041
+ <div class="col-span-1 flex justify-center">
2042
+ <p-checkbox
2043
+ [binary]="true"
2044
+ [formControl]="getColumnControl(column.key, 'isVisible')"
2045
+ [inputId]="'visible_' + column.key"
2046
+ styleClass="[&>div]:border-gray-300 [&>div]:hover:border-gray-400"
2047
+ (onChange)="onFormChange()"
2048
+ >
2049
+ </p-checkbox>
2050
+ </div>
2051
+ <!-- Column Info -->
2052
+ <div class="col-span-12 md:col-span-4">
2053
+ <label [for]="'visible_' + column.key" class="block font-medium text-gray-900 cursor-pointer hover:text-blue-600">
2054
+ {{ column.label }}
2055
+ </label>
2056
+ <p class="text-xs text-gray-500 mt-1">{{ column.key }}</p>
2057
+ </div>
2058
+ <!-- Display Name -->
2059
+ <div class="col-span-12 md:col-span-3">
2060
+ @if (isColumnVisible(column.key)) {
2061
+ <input
2062
+ type="text"
2063
+ [formControl]="getColumnControl(column.key, 'displayName')"
2064
+ [placeholder]="column.label"
2065
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
2066
+ (blur)="onFormChange()"
2067
+ >
2068
+ }
2069
+ </div>
2070
+ <!-- Aggregate Function -->
2071
+ <!-- <div class="col-span-12 md:col-span-3">
2072
+ <div *ngIf="column.aggregatable && isColumnVisible(column.key)">
2073
+ <p-select
2074
+ [options]="aggregateFunctions"
2075
+ optionLabel="label"
2076
+ optionValue="value"
2077
+ [formControl]="getColumnControl(column.key, 'aggregateFunction')"
2078
+ [placeholder]="'SELECT_AGGREGATE' | translate"
2079
+ [showClear]="true"
2080
+ styleClass="w-full"
2081
+ (onChange)="onFormChange()"
2082
+ >
2083
+ </p-select>
2084
+ </div>
2085
+ <span *ngIf="!column.aggregatable && isColumnVisible(column.key)" class="text-xs text-gray-500">
2086
+ {{ 'NO_AGGREGATES_AVAILABLE' | translate }}
2087
+ </span>
2088
+ </div> -->
2089
+ </div>
2090
+ <!-- Aggregate Info -->
2091
+ @if (hasAggregateFunction(column.key) && isColumnVisible(column.key)) {
2092
+ <div
2093
+ class="mt-3 p-3 bg-blue-50 border border-blue-200 rounded-lg">
2094
+ <div class="flex items-center gap-2">
2095
+ <i class="pi pi-info-circle text-blue-500 text-sm"></i>
2096
+ <span class="text-sm text-blue-800">
2097
+ <!-- :{function: getAggregateLabel(getAggregateFunctionValue(column.key))} -->
2098
+ {{ 'AGGREGATE_INFO' | translate }}
2099
+ </span>
2100
+ </div>
2101
+ </div>
2102
+ }
2103
+ </div>
2104
+ }
2105
+ </div>
2106
+
2107
+ <!-- Summary -->
2108
+ <div class="flex flex-col sm:flex-row justify-between items-center gap-4 mt-6 pt-4 border-t border-gray-200">
2109
+ <span class="text-sm text-gray-600">
2110
+ <!-- :{visible: visibleColumnsCount, total: columns.length} -->
2111
+ {{ 'COLUMNS_VISIBLE_COUNT' | translate }}
2112
+ </span>
2113
+ <!-- <div class="flex gap-4 text-sm">
2114
+ <span class="inline-flex items-center gap-1 text-green-600">
2115
+ <i class="pi pi-chart-line"></i>
2116
+ {{ numericColumnsCount }} {{ 'NUMERIC' | translate }}
2117
+ </span>
2118
+ <span class="inline-flex items-center gap-1 text-blue-600">
2119
+ <i class="pi pi-calculator"></i>
2120
+ {{ aggregatedColumnsCount }} {{ 'AGGREGATED' | translate }}
2121
+ </span>
2122
+ </div> -->
2123
+ </div>
2124
+ </div>
2125
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$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: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: SelectModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: DatePickerModule }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i2$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: InputGroupModule }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: ListboxModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2126
+ }
2127
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ColumnsBuilderComponent, decorators: [{
2128
+ type: Component,
2129
+ args: [{
2130
+ selector: 'formly-columns-builder',
2131
+ template: `
2132
+ <div>
2133
+ <!-- Header -->
2134
+ <div class="mb-6">
2135
+ <h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || ('COLUMNS_MANAGEMENT' | translate) }}</h5>
2136
+ <p class="text-gray-600 text-sm">{{ props.description || ('COLUMNS_DESCRIPTION' | translate) }}</p>
2137
+ </div>
2138
+
2139
+ <!-- Quick Actions -->
2140
+ <div class="flex flex-wrap gap-2 mb-6">
2141
+ <button
2142
+ type="button"
2143
+ (click)="toggleAll(true)"
2144
+ class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
2145
+ >
2146
+ <i class="pi pi-eye"></i>
2147
+ {{ 'SHOW_ALL' | translate }}
2148
+ </button>
2149
+ <button
2150
+ type="button"
2151
+ (click)="toggleAll(false)"
2152
+ class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
2153
+ >
2154
+ <i class="pi pi-eye-slash"></i>
2155
+ {{ 'HIDE_ALL' | translate }}
2156
+ </button>
2157
+ <button
2158
+ type="button"
2159
+ (click)="resetToDefault()"
2160
+ class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
2161
+ >
2162
+ <i class="pi pi-refresh"></i>
2163
+ {{ 'RESET_TO_DEFAULT' | translate }}
2164
+ </button>
2165
+ </div>
2166
+
2167
+ <!-- Drag Info -->
2168
+ <div class="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
2169
+ <i class="pi pi-info-circle mr-2"></i>
2170
+ {{ 'DRAG_COLUMNS_INFO' | translate }}
2171
+ </div>
2172
+
2173
+ <!-- Columns List -->
2174
+ <div class="space-y-3">
2175
+ @if (columns.length === 0) {
2176
+ <div class="text-center py-8 px-4 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50">
2177
+ <i class="pi pi-table text-4xl text-gray-400 mb-3"></i>
2178
+ <p class="text-gray-500">{{ 'NO_COLUMNS_AVAILABLE' | translate }}</p>
2179
+ </div>
2180
+ }
2181
+
2182
+ @for (column of columns; track column; let i = $index) {
2183
+ <div
2184
+ class="bg-gray-50 rounded-lg border border-gray-200 p-1 transition-colors hover:bg-gray-100"
2185
+ draggable="true"
2186
+ (dragstart)="onDragStart($event, i)"
2187
+ (dragover)="onDragOver($event)"
2188
+ (drop)="onDrop($event, i)"
2189
+ (dragenter)="onDragEnter($event)"
2190
+ (dragleave)="onDragLeave($event)">
2191
+ <div class="grid grid-cols-12 gap-4 items-center">
2192
+ <!-- Drag Handle -->
2193
+ <div class="col-span-1 flex justify-center cursor-move text-gray-400 hover:text-gray-600"
2194
+ draggable="true"
2195
+ (dragstart)="onDragStart($event, i)">
2196
+ <i class="pi pi-bars"></i>
2197
+ </div>
2198
+ <!-- Visibility Toggle -->
2199
+ <div class="col-span-1 flex justify-center">
2200
+ <p-checkbox
2201
+ [binary]="true"
2202
+ [formControl]="getColumnControl(column.key, 'isVisible')"
2203
+ [inputId]="'visible_' + column.key"
2204
+ styleClass="[&>div]:border-gray-300 [&>div]:hover:border-gray-400"
2205
+ (onChange)="onFormChange()"
2206
+ >
2207
+ </p-checkbox>
2208
+ </div>
2209
+ <!-- Column Info -->
2210
+ <div class="col-span-12 md:col-span-4">
2211
+ <label [for]="'visible_' + column.key" class="block font-medium text-gray-900 cursor-pointer hover:text-blue-600">
2212
+ {{ column.label }}
2213
+ </label>
2214
+ <p class="text-xs text-gray-500 mt-1">{{ column.key }}</p>
2215
+ </div>
2216
+ <!-- Display Name -->
2217
+ <div class="col-span-12 md:col-span-3">
2218
+ @if (isColumnVisible(column.key)) {
2219
+ <input
2220
+ type="text"
2221
+ [formControl]="getColumnControl(column.key, 'displayName')"
2222
+ [placeholder]="column.label"
2223
+ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
2224
+ (blur)="onFormChange()"
2225
+ >
2226
+ }
2227
+ </div>
2228
+ <!-- Aggregate Function -->
2229
+ <!-- <div class="col-span-12 md:col-span-3">
2230
+ <div *ngIf="column.aggregatable && isColumnVisible(column.key)">
2231
+ <p-select
2232
+ [options]="aggregateFunctions"
2233
+ optionLabel="label"
2234
+ optionValue="value"
2235
+ [formControl]="getColumnControl(column.key, 'aggregateFunction')"
2236
+ [placeholder]="'SELECT_AGGREGATE' | translate"
2237
+ [showClear]="true"
2238
+ styleClass="w-full"
2239
+ (onChange)="onFormChange()"
2240
+ >
2241
+ </p-select>
2242
+ </div>
2243
+ <span *ngIf="!column.aggregatable && isColumnVisible(column.key)" class="text-xs text-gray-500">
2244
+ {{ 'NO_AGGREGATES_AVAILABLE' | translate }}
2245
+ </span>
2246
+ </div> -->
2247
+ </div>
2248
+ <!-- Aggregate Info -->
2249
+ @if (hasAggregateFunction(column.key) && isColumnVisible(column.key)) {
2250
+ <div
2251
+ class="mt-3 p-3 bg-blue-50 border border-blue-200 rounded-lg">
2252
+ <div class="flex items-center gap-2">
2253
+ <i class="pi pi-info-circle text-blue-500 text-sm"></i>
2254
+ <span class="text-sm text-blue-800">
2255
+ <!-- :{function: getAggregateLabel(getAggregateFunctionValue(column.key))} -->
2256
+ {{ 'AGGREGATE_INFO' | translate }}
2257
+ </span>
2258
+ </div>
2259
+ </div>
2260
+ }
2261
+ </div>
2262
+ }
2263
+ </div>
2264
+
2265
+ <!-- Summary -->
2266
+ <div class="flex flex-col sm:flex-row justify-between items-center gap-4 mt-6 pt-4 border-t border-gray-200">
2267
+ <span class="text-sm text-gray-600">
2268
+ <!-- :{visible: visibleColumnsCount, total: columns.length} -->
2269
+ {{ 'COLUMNS_VISIBLE_COUNT' | translate }}
2270
+ </span>
2271
+ <!-- <div class="flex gap-4 text-sm">
2272
+ <span class="inline-flex items-center gap-1 text-green-600">
2273
+ <i class="pi pi-chart-line"></i>
2274
+ {{ numericColumnsCount }} {{ 'NUMERIC' | translate }}
2275
+ </span>
2276
+ <span class="inline-flex items-center gap-1 text-blue-600">
2277
+ <i class="pi pi-calculator"></i>
2278
+ {{ aggregatedColumnsCount }} {{ 'AGGREGATED' | translate }}
2279
+ </span>
2280
+ </div> -->
2281
+ </div>
2282
+ </div>
2283
+ `,
2284
+ imports: [
2285
+ FormsModule,
2286
+ ReactiveFormsModule,
2287
+ SelectModule,
2288
+ InputTextModule,
2289
+ ButtonModule,
2290
+ DatePickerModule,
2291
+ CheckboxModule,
2292
+ RadioButtonModule,
2293
+ FormlyForm,
2294
+ TooltipModule,
2295
+ MenuModule,
2296
+ TranslatePipe,
2297
+ InputGroupModule,
2298
+ InputGroupAddonModule,
2299
+ PopoverModule,
2300
+ ListboxModule
2301
+ ]
2302
+ }]
2303
+ }] });
2304
+
2305
+ // group-builder.component.ts
2306
+ class GroupBuilderComponent extends FieldType {
2307
+ _groups;
2308
+ dragStartIndex = -1;
2309
+ get groups() {
2310
+ return this._groups;
2311
+ }
2312
+ get availableFields() {
2313
+ return this.props['fields'] || [];
2314
+ }
2315
+ get maxGroups() {
2316
+ return this.props['maxGroups'] || 3;
2317
+ }
2318
+ ngOnInit() {
2319
+ this.initializeFormArray();
2320
+ this.setupValueChanges();
2321
+ }
2322
+ initializeFormArray() {
2323
+ const parentForm = this.form;
2324
+ const fieldKey = this.field.key;
2325
+ if (!fieldKey)
2326
+ return;
2327
+ // Get or create the FormArray in the parent form
2328
+ let formArray = parentForm.get(fieldKey);
2329
+ if (!formArray || !(formArray instanceof FormArray)) {
2330
+ // Create new FormArray
2331
+ formArray = new FormArray([]);
2332
+ parentForm.setControl(fieldKey, formArray);
2333
+ // Initialize with existing model data
2334
+ const initialValue = this.model[fieldKey];
2335
+ if (Array.isArray(initialValue)) {
2336
+ initialValue.forEach(group => {
2337
+ formArray.push(this.createGroupControl(group));
2338
+ });
2339
+ }
2340
+ }
2341
+ // Store the FormArray reference
2342
+ this._groups = formArray;
2343
+ }
2344
+ setupValueChanges() {
2345
+ // Listen to form array changes and update the model
2346
+ this.groups.valueChanges.subscribe(() => {
2347
+ this.updateModel();
2348
+ });
2349
+ }
2350
+ updateModel() {
2351
+ const fieldKey = this.field.key;
2352
+ if (fieldKey) {
2353
+ // Update the model with current form values
2354
+ this.model[fieldKey] = this.groups.value;
2355
+ // Trigger form control update to notify Formly
2356
+ this.formControl.setValue(this.groups.value);
2357
+ // Mark form control as touched/dirty if needed
2358
+ this.formControl.markAsTouched();
2359
+ this.formControl.markAsDirty();
2360
+ }
2361
+ }
2362
+ // Called when form controls change
2363
+ onFormChange() {
2364
+ this.updateModel();
2365
+ }
2366
+ createGroupControl(group) {
2367
+ return new FormGroup({
2368
+ field: new FormControl(group?.field || ''),
2369
+ displayName: new FormControl(group?.displayName || ''),
2370
+ showTotal: new FormControl(group?.showTotal !== false)
2371
+ });
2372
+ }
2373
+ addGroup() {
2374
+ if (this.canAddMoreGroups()) {
2375
+ this.groups.push(this.createGroupControl());
2376
+ this.updateModel();
2377
+ }
2378
+ }
2379
+ removeGroup(index) {
2380
+ this.groups.removeAt(index);
2381
+ this.updateModel();
2382
+ }
2383
+ // Drag & Drop Methods
2384
+ onDragStart(event, index) {
2385
+ this.dragStartIndex = index;
2386
+ event.dataTransfer?.setData('text/plain', index.toString());
2387
+ event.currentTarget.classList.add('opacity-50', 'border-blue-300');
2388
+ }
2389
+ onDragOver(event) {
2390
+ event.preventDefault();
2391
+ }
2392
+ onDrop(event, dropIndex) {
2393
+ event.preventDefault();
2394
+ const dragIndex = this.dragStartIndex;
2395
+ if (dragIndex !== -1 && dragIndex !== dropIndex) {
2396
+ this.moveGroup(dragIndex, dropIndex);
2397
+ }
2398
+ this.removeDragStyles(event);
2399
+ }
2400
+ onDragEnter(event) {
2401
+ event.currentTarget.classList.add('border-blue-500', 'bg-blue-50', 'border-2');
2402
+ }
2403
+ onDragLeave(event) {
2404
+ this.removeDragStyles(event);
2405
+ }
2406
+ onDragEnd(event) {
2407
+ this.removeDragStyles(event);
2408
+ this.dragStartIndex = -1;
2409
+ }
2410
+ removeDragStyles(event) {
2411
+ const element = event.currentTarget;
2412
+ element.classList.remove('border-blue-500', 'bg-blue-50', 'opacity-50', 'border-blue-300', 'border-2');
2413
+ }
2414
+ moveGroup(fromIndex, toIndex) {
2415
+ const movedGroup = this.groups.at(fromIndex);
2416
+ this.groups.removeAt(fromIndex);
2417
+ this.groups.insert(toIndex, movedGroup);
2418
+ this.updateModel();
2419
+ }
2420
+ getGroupControl(index, controlName) {
2421
+ const group = this.groups.at(index);
2422
+ return group.get(controlName);
2423
+ }
2424
+ onFieldChange(index, fieldKey) {
2425
+ const displayNameControl = this.getGroupControl(index, 'displayName');
2426
+ if (!displayNameControl.value && fieldKey) {
2427
+ const field = this.availableFields.find(f => f.key === fieldKey);
2428
+ if (field) {
2429
+ displayNameControl.setValue(field.label);
2430
+ }
2431
+ }
2432
+ this.updateModel();
2433
+ }
2434
+ getFieldLabel(fieldKey) {
2435
+ const field = this.availableFields.find(f => f.key === fieldKey);
2436
+ return field ? field.label : fieldKey;
2437
+ }
2438
+ canAddMoreGroups() {
2439
+ return this.groups.length < this.maxGroups;
2440
+ }
2441
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GroupBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2442
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GroupBuilderComponent, isStandalone: true, selector: "formly-group-builder", usesInheritance: true, ngImport: i0, template: `<div>
2443
+ <!-- Header -->
2444
+ <div class="mb-6">
2445
+ <h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || ('DATA_GROUPING' | translate) }}</h5>
2446
+ <p class="text-gray-600 text-sm">{{ props.description || ('GROUP_DESCRIPTION' | translate) }}</p>
2447
+ </div>
2448
+
2449
+ <!-- Group Configuration -->
2450
+ <div class="space-y-4">
2451
+ <div class="flex items-center justify-between">
2452
+ <span class="font-medium text-gray-700">{{ 'GROUP_LEVELS' | translate }}</span>
2453
+ @if (canAddMoreGroups()) {
2454
+ <button
2455
+ type="button"
2456
+ (click)="addGroup()"
2457
+ [disabled]="!canAddMoreGroups()"
2458
+ class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-lg hover:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
2459
+ >
2460
+ <i class="pi pi-plus"></i>
2461
+ {{ 'ADD_GROUP_LEVEL' | translate }}
2462
+ </button>
2463
+ }
2464
+ </div>
2465
+
2466
+ <!-- Drag Info -->
2467
+ @if (groups.length > 1) {
2468
+ <div class="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
2469
+ <i class="pi pi-info-circle mr-2"></i>
2470
+ {{ 'DRAG_INFO' | translate }}
2471
+ </div>
2472
+ }
2473
+
2474
+ <!-- Empty State -->
2475
+ @if (groups.length === 0) {
2476
+ <div class="text-center py-8 px-4 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50">
2477
+ <i class="pi pi-layer-group text-4xl text-gray-400 mb-3"></i>
2478
+ <p class="text-gray-500 mb-4">{{ 'NO_GROUPING_LEVELS' | translate }}</p>
2479
+ <button
2480
+ type="button"
2481
+ (click)="addGroup()"
2482
+ class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors"
2483
+ >
2484
+ <i class="pi pi-plus"></i>
2485
+ {{ 'ADD_FIRST_GROUP_LEVEL' | translate }}
2486
+ </button>
2487
+ </div>
2488
+ }
2489
+
2490
+ <!-- Groups List with Drag & Drop -->
2491
+ <div class="space-y-3">
2492
+ @for (group of groups.controls; track group; let i = $index) {
2493
+ <div
2494
+ class="bg-gray-50 rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-100"
2495
+ draggable="true"
2496
+ (dragstart)="onDragStart($event, i)"
2497
+ (dragover)="onDragOver($event)"
2498
+ (drop)="onDrop($event, i)"
2499
+ (dragenter)="onDragEnter($event)"
2500
+ (dragleave)="onDragLeave($event)"
2501
+ (dragend)="onDragEnd($event)">
2502
+ <div class="grid grid-cols-12 gap-4 items-start">
2503
+ <!-- Drag Handle -->
2504
+ @if (groups.length > 1) {
2505
+ <div
2506
+ class="col-span-1 flex justify-center cursor-move text-gray-400 hover:text-gray-600 pt-2"
2507
+ draggable="true"
2508
+ (dragstart)="onDragStart($event, i)">
2509
+ <i class="pi pi-bars"></i>
2510
+ </div>
2511
+ }
2512
+ <!-- Group Content -->
2513
+ <div class="col-span-11">
2514
+ <div class="flex items-center justify-between mb-3">
2515
+ <div class="flex items-center gap-2">
2516
+ <i class="pi pi-sort-alt text-gray-500"></i>
2517
+ <span class="font-medium text-gray-700">{{ 'GROUP_LEVEL' | translate }} {{ i + 1 }}</span>
2518
+ </div>
2519
+ <div class="flex items-center gap-1">
2520
+ <button
2521
+ type="button"
2522
+ (click)="removeGroup(i)"
2523
+ class="p-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded transition-colors"
2524
+ [pTooltip]="'REMOVE' | translate"
2525
+ >
2526
+ <i class="pi pi-times"></i>
2527
+ </button>
2528
+ </div>
2529
+ </div>
2530
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2531
+ <!-- Field Selection -->
2532
+ <div>
2533
+ <label class="block text-sm font-medium text-gray-700 mb-2">{{ 'FIELD' | translate }}</label>
2534
+ <p-select
2535
+ [options]="availableFields"
2536
+ optionLabel="label"
2537
+ optionValue="key"
2538
+ [formControl]="getGroupControl(i, 'field')"
2539
+ [placeholder]="'SELECT_FIELD' | translate"
2540
+ [showClear]="true"
2541
+ (onChange)="onFieldChange(i, $event.value)"
2542
+ styleClass="w-full"
2543
+ >
2544
+ <ng-template pTemplate="selectedItem">
2545
+ @if (getGroupControl(i, 'field').value) {
2546
+ <span class="text-gray-900">
2547
+ {{ getFieldLabel(getGroupControl(i, 'field').value) }}
2548
+ </span>
2549
+ }
2550
+ @if (!getGroupControl(i, 'field').value) {
2551
+ <span class="text-gray-500">{{ 'SELECT_FIELD' | translate }}</span>
2552
+ }
2553
+ </ng-template>
2554
+ </p-select>
2555
+ </div>
2556
+ <!-- Display Name -->
2557
+ <div>
2558
+ <label class="block text-sm font-medium text-gray-700 mb-2">{{ 'DISPLAY_NAME' | translate }}</label>
2559
+ <input
2560
+ type="text"
2561
+ pInputText
2562
+ [formControl]="getGroupControl(i, 'displayName')"
2563
+ [placeholder]="'DISPLAY_NAME_PLACEHOLDER' | translate"
2564
+ class="w-full text-gray-900"
2565
+ (blur)="onFormChange()"
2566
+ >
2567
+ </div>
2568
+ </div>
2569
+ <!-- Show Total Toggle -->
2570
+ <div class="flex items-center gap-2 mt-3">
2571
+ <p-checkbox
2572
+ [binary]="true"
2573
+ [formControl]="getGroupControl(i, 'showTotal')"
2574
+ [inputId]="'showTotal' + i"
2575
+ styleClass="[&>div]:border-gray-300 [&>div]:hover:border-gray-400"
2576
+ (onChange)="onFormChange()"
2577
+ >
2578
+ </p-checkbox>
2579
+ <label [for]="'showTotal' + i" class="text-sm text-gray-700 cursor-pointer hover:text-gray-900">
2580
+ {{ 'SHOW_TOTAL' | translate }}
2581
+ </label>
2582
+ </div>
2583
+ </div>
2584
+ </div>
2585
+ </div>
2586
+ }
2587
+ </div>
2588
+ </div>
2589
+
2590
+ <!-- Grouping Tips -->
2591
+ @if (groups.length > 0) {
2592
+ <div class="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
2593
+ <div class="flex items-center gap-3">
2594
+ <i class="pi pi-info-circle text-blue-500"></i>
2595
+ <div>
2596
+ <p class="text-sm text-blue-800">
2597
+ <!-- :{count: groups.length} -->
2598
+ {{ 'GROUPING_TIPS' | translate }}
2599
+ {{ groups.length }} {{ 'LEVELS_DEFINED' | translate }}
2600
+ </p>
2601
+ </div>
2602
+ </div>
2603
+ </div>
2604
+ }
2605
+ </div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$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: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i3.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: i1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: DatePickerModule }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i2$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip"] }, { kind: "ngmodule", type: MenuModule }, { kind: "ngmodule", type: InputGroupModule }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: ListboxModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2606
+ }
2607
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GroupBuilderComponent, decorators: [{
2608
+ type: Component,
2609
+ args: [{
2610
+ selector: 'formly-group-builder',
2611
+ template: `<div>
2612
+ <!-- Header -->
2613
+ <div class="mb-6">
2614
+ <h5 class="text-lg font-semibold text-gray-900 mb-2">{{ props.label || ('DATA_GROUPING' | translate) }}</h5>
2615
+ <p class="text-gray-600 text-sm">{{ props.description || ('GROUP_DESCRIPTION' | translate) }}</p>
2616
+ </div>
2617
+
2618
+ <!-- Group Configuration -->
2619
+ <div class="space-y-4">
2620
+ <div class="flex items-center justify-between">
2621
+ <span class="font-medium text-gray-700">{{ 'GROUP_LEVELS' | translate }}</span>
2622
+ @if (canAddMoreGroups()) {
2623
+ <button
2624
+ type="button"
2625
+ (click)="addGroup()"
2626
+ [disabled]="!canAddMoreGroups()"
2627
+ class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-lg hover:bg-blue-100 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
2628
+ >
2629
+ <i class="pi pi-plus"></i>
2630
+ {{ 'ADD_GROUP_LEVEL' | translate }}
2631
+ </button>
2632
+ }
2633
+ </div>
2634
+
2635
+ <!-- Drag Info -->
2636
+ @if (groups.length > 1) {
2637
+ <div class="mb-4 p-2 bg-blue-50 border border-blue-200 rounded text-sm text-blue-700">
2638
+ <i class="pi pi-info-circle mr-2"></i>
2639
+ {{ 'DRAG_INFO' | translate }}
2640
+ </div>
2641
+ }
2642
+
2643
+ <!-- Empty State -->
2644
+ @if (groups.length === 0) {
2645
+ <div class="text-center py-8 px-4 border-2 border-dashed border-gray-300 rounded-lg bg-gray-50">
2646
+ <i class="pi pi-layer-group text-4xl text-gray-400 mb-3"></i>
2647
+ <p class="text-gray-500 mb-4">{{ 'NO_GROUPING_LEVELS' | translate }}</p>
2648
+ <button
2649
+ type="button"
2650
+ (click)="addGroup()"
2651
+ class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors"
2652
+ >
2653
+ <i class="pi pi-plus"></i>
2654
+ {{ 'ADD_FIRST_GROUP_LEVEL' | translate }}
2655
+ </button>
2656
+ </div>
2657
+ }
2658
+
2659
+ <!-- Groups List with Drag & Drop -->
2660
+ <div class="space-y-3">
2661
+ @for (group of groups.controls; track group; let i = $index) {
2662
+ <div
2663
+ class="bg-gray-50 rounded-lg border border-gray-200 p-4 transition-colors hover:bg-gray-100"
2664
+ draggable="true"
2665
+ (dragstart)="onDragStart($event, i)"
2666
+ (dragover)="onDragOver($event)"
2667
+ (drop)="onDrop($event, i)"
2668
+ (dragenter)="onDragEnter($event)"
2669
+ (dragleave)="onDragLeave($event)"
2670
+ (dragend)="onDragEnd($event)">
2671
+ <div class="grid grid-cols-12 gap-4 items-start">
2672
+ <!-- Drag Handle -->
2673
+ @if (groups.length > 1) {
2674
+ <div
2675
+ class="col-span-1 flex justify-center cursor-move text-gray-400 hover:text-gray-600 pt-2"
2676
+ draggable="true"
2677
+ (dragstart)="onDragStart($event, i)">
2678
+ <i class="pi pi-bars"></i>
2679
+ </div>
2680
+ }
2681
+ <!-- Group Content -->
2682
+ <div class="col-span-11">
2683
+ <div class="flex items-center justify-between mb-3">
2684
+ <div class="flex items-center gap-2">
2685
+ <i class="pi pi-sort-alt text-gray-500"></i>
2686
+ <span class="font-medium text-gray-700">{{ 'GROUP_LEVEL' | translate }} {{ i + 1 }}</span>
2687
+ </div>
2688
+ <div class="flex items-center gap-1">
2689
+ <button
2690
+ type="button"
2691
+ (click)="removeGroup(i)"
2692
+ class="p-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded transition-colors"
2693
+ [pTooltip]="'REMOVE' | translate"
2694
+ >
2695
+ <i class="pi pi-times"></i>
2696
+ </button>
2697
+ </div>
2698
+ </div>
2699
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
2700
+ <!-- Field Selection -->
2701
+ <div>
2702
+ <label class="block text-sm font-medium text-gray-700 mb-2">{{ 'FIELD' | translate }}</label>
2703
+ <p-select
2704
+ [options]="availableFields"
2705
+ optionLabel="label"
2706
+ optionValue="key"
2707
+ [formControl]="getGroupControl(i, 'field')"
2708
+ [placeholder]="'SELECT_FIELD' | translate"
2709
+ [showClear]="true"
2710
+ (onChange)="onFieldChange(i, $event.value)"
2711
+ styleClass="w-full"
2712
+ >
2713
+ <ng-template pTemplate="selectedItem">
2714
+ @if (getGroupControl(i, 'field').value) {
2715
+ <span class="text-gray-900">
2716
+ {{ getFieldLabel(getGroupControl(i, 'field').value) }}
2717
+ </span>
2718
+ }
2719
+ @if (!getGroupControl(i, 'field').value) {
2720
+ <span class="text-gray-500">{{ 'SELECT_FIELD' | translate }}</span>
2721
+ }
2722
+ </ng-template>
2723
+ </p-select>
2724
+ </div>
2725
+ <!-- Display Name -->
2726
+ <div>
2727
+ <label class="block text-sm font-medium text-gray-700 mb-2">{{ 'DISPLAY_NAME' | translate }}</label>
2728
+ <input
2729
+ type="text"
2730
+ pInputText
2731
+ [formControl]="getGroupControl(i, 'displayName')"
2732
+ [placeholder]="'DISPLAY_NAME_PLACEHOLDER' | translate"
2733
+ class="w-full text-gray-900"
2734
+ (blur)="onFormChange()"
2735
+ >
2736
+ </div>
2737
+ </div>
2738
+ <!-- Show Total Toggle -->
2739
+ <div class="flex items-center gap-2 mt-3">
2740
+ <p-checkbox
2741
+ [binary]="true"
2742
+ [formControl]="getGroupControl(i, 'showTotal')"
2743
+ [inputId]="'showTotal' + i"
2744
+ styleClass="[&>div]:border-gray-300 [&>div]:hover:border-gray-400"
2745
+ (onChange)="onFormChange()"
2746
+ >
2747
+ </p-checkbox>
2748
+ <label [for]="'showTotal' + i" class="text-sm text-gray-700 cursor-pointer hover:text-gray-900">
2749
+ {{ 'SHOW_TOTAL' | translate }}
2750
+ </label>
2751
+ </div>
2752
+ </div>
2753
+ </div>
2754
+ </div>
2755
+ }
2756
+ </div>
2757
+ </div>
2758
+
2759
+ <!-- Grouping Tips -->
2760
+ @if (groups.length > 0) {
2761
+ <div class="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
2762
+ <div class="flex items-center gap-3">
2763
+ <i class="pi pi-info-circle text-blue-500"></i>
2764
+ <div>
2765
+ <p class="text-sm text-blue-800">
2766
+ <!-- :{count: groups.length} -->
2767
+ {{ 'GROUPING_TIPS' | translate }}
2768
+ {{ groups.length }} {{ 'LEVELS_DEFINED' | translate }}
2769
+ </p>
2770
+ </div>
2771
+ </div>
2772
+ </div>
2773
+ }
2774
+ </div>`,
2775
+ imports: [
2776
+ FormsModule,
2777
+ ReactiveFormsModule,
2778
+ SelectModule,
2779
+ InputTextModule,
2780
+ ButtonModule,
2781
+ DatePickerModule,
2782
+ CheckboxModule,
2783
+ RadioButtonModule,
2784
+ FormlyForm,
2785
+ TooltipModule,
2786
+ MenuModule,
2787
+ TranslatePipe,
2788
+ InputGroupModule,
2789
+ InputGroupAddonModule,
2790
+ PopoverModule,
2791
+ ListboxModule
2792
+ ]
2793
+ }]
2794
+ }] });
2795
+
2796
+ // group-type.component.ts
2797
+ class GroupTypeComponent extends FieldType {
2798
+ collapsed = this.props.collapsedByDefault ?? false;
2799
+ get showValidation() {
2800
+ return this.props.showValidation !== false &&
2801
+ this.formControl.invalid && (this.formControl.dirty || this.formControl.touched);
2802
+ }
2803
+ get validationIcon() {
2804
+ if (this.formControl.valid)
2805
+ return 'pi pi-check-circle text-green-500';
2806
+ if (this.formControl.invalid)
2807
+ return 'pi pi-exclamation-circle text-red-500';
2808
+ return 'pi pi-circle text-500';
2809
+ }
2810
+ get validationText() {
2811
+ return this.formControl.valid ? 'جميع الحقول صالحة' : 'هناك أخطاء في بعض الحقول';
2812
+ }
2813
+ get groupClass() {
2814
+ const v = this.props.variant || 'default';
2815
+ const map = {
2816
+ default: 'border-round border-1 border-200 bg-white mb-3',
2817
+ card: 'border-round surface-card shadow-sm mb-3',
2818
+ bordered: 'border-round border-1 border-300 surface-section mb-3',
2819
+ transparent: 'border-0 bg-transparent mb-3'
2820
+ };
2821
+ return map[v];
2822
+ }
2823
+ get gridClass() {
2824
+ const cols = this.props.columns || 1;
2825
+ const gap = this.props.gap || 'normal';
2826
+ const gaps = { none: 'gap-0', small: 'gap-2', normal: 'gap-3', large: 'gap-4' };
2827
+ return `grid grid-cols-1 ${cols > 1 ? 'md:grid-cols-' + cols : ''} ${gaps[gap]}`;
2828
+ }
2829
+ toggle() {
2830
+ this.collapsed = !this.collapsed;
2831
+ }
2832
+ isRequired() {
2833
+ return this.field.fieldGroup?.some(f => f.props?.required || f.validators?.['required']) ?? false;
2834
+ }
2835
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GroupTypeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2836
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: GroupTypeComponent, isStandalone: true, selector: "formly-group-type", usesInheritance: true, ngImport: i0, template: `
2837
+ <div [class]="groupClass">
2838
+ @if (props.label || props.collapsible) {
2839
+ <div class="flex justify-between items-center p-2"
2840
+ (click)="props.collapsible && toggle()">
2841
+ <div class="flex items-center gap-2">
2842
+ @if (props.collapsible) {
2843
+ <i [class]="collapsed ? 'pi pi-chevron-down' : 'pi pi-chevron-up'"></i>
2844
+ }
2845
+ @if (props.label) {
2846
+ <label class="font-semibold">{{ props.label }}</label>
2847
+ }
2848
+ @if (isRequired()) {
2849
+ <i class="pi pi-asterisk text-red-500 text-xs" pTooltip="حقل مطلوب"></i>
2850
+ }
2851
+ </div>
2852
+ <div class="flex gap-2">
2853
+ @if (props.description && !collapsed) {
2854
+ <i class="pi pi-info-circle text-500"
2855
+ pTooltip="{{ props.description }}"></i>
2856
+ }
2857
+ @if (showValidation) {
2858
+ <i [class]="validationIcon" [pTooltip]="validationText"></i>
2859
+ }
2860
+ @for (a of props.actions || []; track a) {
2861
+ <button pButton type="button" class="p-button-text p-button-sm"
2862
+ [icon]="a.icon" [pTooltip]="a.tooltip" [ngClass]="a.styleClass"
2863
+ (click)="a.handler?.(field, form, this); $event.stopPropagation()"></button>
2864
+ }
2865
+ </div>
2866
+ </div>
2867
+ }
2868
+
2869
+ @if (!collapsed) {
2870
+ <div class="p-3 pt-0" [ngClass]="props.compact ? 'compact-layout' : ''">
2871
+ @if (props.description && props.showDescription) {
2872
+ <p class="text-500 text-sm mb-2">{{ props.description }}</p>
2873
+ }
2874
+ <div [ngClass]="gridClass">
2875
+ @for (f of field.fieldGroup; track f) {
2876
+ <formly-field [field]="f"></formly-field>
2877
+ @if (f.props?.description) {
2878
+ <small
2879
+ class="block text-500 text-xs mt-1 ml-1">
2880
+ {{ f.props?.description }}
2881
+ </small>
2882
+ }
2883
+ }
2884
+ </div>
2885
+ @if (!field.fieldGroup?.length) {
2886
+ <div class="text-center p-4 surface-section border-round">
2887
+ <i class="pi pi-inbox text-4xl text-500 mb-2"></i>
2888
+ <p class="text-500">لا توجد حقول</p>
2889
+ </div>
2890
+ }
2891
+ </div>
2892
+ }
2893
+ </div>
2894
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: FormlyField, selector: "formly-field", inputs: ["field"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { 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", "ptTooltip"] }, { kind: "ngmodule", type: BadgeModule }] });
2895
+ }
2896
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: GroupTypeComponent, decorators: [{
2897
+ type: Component,
2898
+ args: [{
2899
+ selector: 'formly-group-type',
2900
+ standalone: true,
2901
+ imports: [CommonModule, FormlyField, ButtonModule, TooltipModule, BadgeModule],
2902
+ template: `
2903
+ <div [class]="groupClass">
2904
+ @if (props.label || props.collapsible) {
2905
+ <div class="flex justify-between items-center p-2"
2906
+ (click)="props.collapsible && toggle()">
2907
+ <div class="flex items-center gap-2">
2908
+ @if (props.collapsible) {
2909
+ <i [class]="collapsed ? 'pi pi-chevron-down' : 'pi pi-chevron-up'"></i>
2910
+ }
2911
+ @if (props.label) {
2912
+ <label class="font-semibold">{{ props.label }}</label>
2913
+ }
2914
+ @if (isRequired()) {
2915
+ <i class="pi pi-asterisk text-red-500 text-xs" pTooltip="حقل مطلوب"></i>
2916
+ }
2917
+ </div>
2918
+ <div class="flex gap-2">
2919
+ @if (props.description && !collapsed) {
2920
+ <i class="pi pi-info-circle text-500"
2921
+ pTooltip="{{ props.description }}"></i>
2922
+ }
2923
+ @if (showValidation) {
2924
+ <i [class]="validationIcon" [pTooltip]="validationText"></i>
2925
+ }
2926
+ @for (a of props.actions || []; track a) {
2927
+ <button pButton type="button" class="p-button-text p-button-sm"
2928
+ [icon]="a.icon" [pTooltip]="a.tooltip" [ngClass]="a.styleClass"
2929
+ (click)="a.handler?.(field, form, this); $event.stopPropagation()"></button>
2930
+ }
2931
+ </div>
2932
+ </div>
2933
+ }
2934
+
2935
+ @if (!collapsed) {
2936
+ <div class="p-3 pt-0" [ngClass]="props.compact ? 'compact-layout' : ''">
2937
+ @if (props.description && props.showDescription) {
2938
+ <p class="text-500 text-sm mb-2">{{ props.description }}</p>
2939
+ }
2940
+ <div [ngClass]="gridClass">
2941
+ @for (f of field.fieldGroup; track f) {
2942
+ <formly-field [field]="f"></formly-field>
2943
+ @if (f.props?.description) {
2944
+ <small
2945
+ class="block text-500 text-xs mt-1 ml-1">
2946
+ {{ f.props?.description }}
2947
+ </small>
2948
+ }
2949
+ }
2950
+ </div>
2951
+ @if (!field.fieldGroup?.length) {
2952
+ <div class="text-center p-4 surface-section border-round">
2953
+ <i class="pi pi-inbox text-4xl text-500 mb-2"></i>
2954
+ <p class="text-500">لا توجد حقول</p>
2955
+ </div>
2956
+ }
2957
+ </div>
2958
+ }
2959
+ </div>
2960
+ `
2961
+ }]
2962
+ }] });
2963
+
2964
+ const formlyConfig = provideFormlyConfig({
2965
+ types: [
2966
+ {
2967
+ name: 'tab-type',
2968
+ component: TabTypeComponent
2969
+ },
2970
+ {
2971
+ name: 'query-builder',
2972
+ component: QueryBuilderComponent
2973
+ },
2974
+ {
2975
+ name: 'sort-builder',
2976
+ component: SortBuilderComponent
2977
+ },
2978
+ {
2979
+ name: 'group-builder',
2980
+ component: GroupBuilderComponent
2981
+ },
2982
+ {
2983
+ name: 'columns-builder',
2984
+ component: ColumnsBuilderComponent
2985
+ },
2986
+ { name: 'group', component: GroupTypeComponent },
2987
+ ],
2988
+ });
2989
+
2990
+ /**
2991
+ * Generated bundle index. Do not edit.
2992
+ */
2993
+
2994
+ export { ColumnsBuilderComponent, GenericSearchAdvanced, GenericSearchAdvancedModule, GroupBuilderComponent, GroupTypeComponent, QueryBuilderComponent, QueryBuilderService, SortBuilderComponent, TabTypeComponent, formlyConfig };
2995
+ //# sourceMappingURL=es.framework-ng.ui.core-generic-search-advanced.mjs.map