@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.
- package/README.md +3 -0
- package/attachments/README.md +3 -0
- package/base-crud/README.md +3 -0
- package/breadcrumb/README.md +3 -0
- package/checkbox/README.md +3 -0
- package/collapsible/README.md +3 -0
- package/color-picker/README.md +3 -0
- package/column-settings-popover/README.md +3 -0
- package/custom-switch/README.md +3 -0
- package/datepicker/README.md +3 -0
- package/deactivation-reason/README.md +3 -0
- package/excel-import/README.md +3 -0
- package/fesm2022/es.framework-ng.ui.core-attachments.mjs +217 -0
- package/fesm2022/es.framework-ng.ui.core-attachments.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-base-crud.mjs +370 -0
- package/fesm2022/es.framework-ng.ui.core-base-crud.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-breadcrumb.mjs +145 -0
- package/fesm2022/es.framework-ng.ui.core-breadcrumb.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-checkbox.mjs +131 -0
- package/fesm2022/es.framework-ng.ui.core-checkbox.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-collapsible.mjs +90 -0
- package/fesm2022/es.framework-ng.ui.core-collapsible.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-color-picker.mjs +87 -0
- package/fesm2022/es.framework-ng.ui.core-color-picker.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-column-settings-popover.mjs +183 -0
- package/fesm2022/es.framework-ng.ui.core-column-settings-popover.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-custom-switch.mjs +119 -0
- package/fesm2022/es.framework-ng.ui.core-custom-switch.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-datepicker.mjs +519 -0
- package/fesm2022/es.framework-ng.ui.core-datepicker.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-deactivation-reason.mjs +144 -0
- package/fesm2022/es.framework-ng.ui.core-deactivation-reason.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-excel-import.mjs +566 -0
- package/fesm2022/es.framework-ng.ui.core-excel-import.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-filter-templates.mjs +386 -0
- package/fesm2022/es.framework-ng.ui.core-filter-templates.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-form-button.mjs +62 -0
- package/fesm2022/es.framework-ng.ui.core-form-button.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-form-field.mjs +155 -0
- package/fesm2022/es.framework-ng.ui.core-form-field.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-form-template.mjs +37 -0
- package/fesm2022/es.framework-ng.ui.core-form-template.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-avatar-image.mjs +132 -0
- package/fesm2022/es.framework-ng.ui.core-formly-avatar-image.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-avatar-label.mjs +89 -0
- package/fesm2022/es.framework-ng.ui.core-formly-avatar-label.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-button-selector.mjs +195 -0
- package/fesm2022/es.framework-ng.ui.core-formly-button-selector.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-button.mjs +73 -0
- package/fesm2022/es.framework-ng.ui.core-formly-button.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-presets.mjs +201 -0
- package/fesm2022/es.framework-ng.ui.core-formly-presets.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-prime-icon-picker.mjs +542 -0
- package/fesm2022/es.framework-ng.ui.core-formly-prime-icon-picker.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-split-button.mjs +62 -0
- package/fesm2022/es.framework-ng.ui.core-formly-split-button.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-ui-all.mjs +422 -0
- package/fesm2022/es.framework-ng.ui.core-formly-ui-all.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-ui.mjs +48 -0
- package/fesm2022/es.framework-ng.ui.core-formly-ui.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-formly-username-with-domain.mjs +85 -0
- package/fesm2022/es.framework-ng.ui.core-formly-username-with-domain.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-assets.mjs +22 -0
- package/fesm2022/es.framework-ng.ui.core-generic-assets.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-autocomplete.mjs +173 -0
- package/fesm2022/es.framework-ng.ui.core-generic-autocomplete.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-button.mjs +150 -0
- package/fesm2022/es.framework-ng.ui.core-generic-button.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-card.mjs +218 -0
- package/fesm2022/es.framework-ng.ui.core-generic-card.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-crud-table.mjs +929 -0
- package/fesm2022/es.framework-ng.ui.core-generic-crud-table.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-dialog.mjs +321 -0
- package/fesm2022/es.framework-ng.ui.core-generic-dialog.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-errormessage.mjs +35 -0
- package/fesm2022/es.framework-ng.ui.core-generic-errormessage.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-formly-fields.mjs +63 -0
- package/fesm2022/es.framework-ng.ui.core-generic-formly-fields.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-loadingspinner.mjs +34 -0
- package/fesm2022/es.framework-ng.ui.core-generic-loadingspinner.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-report-tabs.mjs +196 -0
- package/fesm2022/es.framework-ng.ui.core-generic-report-tabs.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-report.mjs +556 -0
- package/fesm2022/es.framework-ng.ui.core-generic-report.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-search-advanced.mjs +2995 -0
- package/fesm2022/es.framework-ng.ui.core-generic-search-advanced.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-search.mjs +103 -0
- package/fesm2022/es.framework-ng.ui.core-generic-search.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-selector.mjs +684 -0
- package/fesm2022/es.framework-ng.ui.core-generic-selector.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-table.mjs +388 -0
- package/fesm2022/es.framework-ng.ui.core-generic-table.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-generic-view.mjs +465 -0
- package/fesm2022/es.framework-ng.ui.core-generic-view.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-header-wrapper.mjs +31 -0
- package/fesm2022/es.framework-ng.ui.core-header-wrapper.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-icon-picker.mjs +205 -0
- package/fesm2022/es.framework-ng.ui.core-icon-picker.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-input-switch.mjs +102 -0
- package/fesm2022/es.framework-ng.ui.core-input-switch.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-input-with-icon.mjs +67 -0
- package/fesm2022/es.framework-ng.ui.core-input-with-icon.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-input.mjs +250 -0
- package/fesm2022/es.framework-ng.ui.core-input.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-label-type.mjs +184 -0
- package/fesm2022/es.framework-ng.ui.core-label-type.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-loading-skeletons.mjs +66 -0
- package/fesm2022/es.framework-ng.ui.core-loading-skeletons.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-odata-query-builder.mjs +307 -0
- package/fesm2022/es.framework-ng.ui.core-odata-query-builder.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-query-type.mjs +162 -0
- package/fesm2022/es.framework-ng.ui.core-query-type.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-radio.mjs +120 -0
- package/fesm2022/es.framework-ng.ui.core-radio.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-repeat.mjs +290 -0
- package/fesm2022/es.framework-ng.ui.core-repeat.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-select.mjs +179 -0
- package/fesm2022/es.framework-ng.ui.core-select.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-sidebar-cards.mjs +52 -0
- package/fesm2022/es.framework-ng.ui.core-sidebar-cards.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-sidebar-toggles.mjs +53 -0
- package/fesm2022/es.framework-ng.ui.core-sidebar-toggles.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-tabs.mjs +57 -0
- package/fesm2022/es.framework-ng.ui.core-tabs.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-tag-type.mjs +209 -0
- package/fesm2022/es.framework-ng.ui.core-tag-type.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-text-editor.mjs +58 -0
- package/fesm2022/es.framework-ng.ui.core-text-editor.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-textarea.mjs +93 -0
- package/fesm2022/es.framework-ng.ui.core-textarea.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core-wrappers.mjs +245 -0
- package/fesm2022/es.framework-ng.ui.core-wrappers.mjs.map +1 -0
- package/fesm2022/es.framework-ng.ui.core.mjs +20 -0
- package/fesm2022/es.framework-ng.ui.core.mjs.map +1 -0
- package/filter-templates/README.md +3 -0
- package/form-button/README.md +3 -0
- package/form-field/README.md +3 -0
- package/form-template/README.md +3 -0
- package/formly-avatar-image/README.md +3 -0
- package/formly-avatar-label/README.md +3 -0
- package/formly-button/README.md +3 -0
- package/formly-button-selector/README.md +3 -0
- package/formly-presets/README.md +3 -0
- package/formly-prime-icon-picker/README.md +3 -0
- package/formly-split-button/README.md +3 -0
- package/formly-ui/README.md +3 -0
- package/formly-ui-all/README.md +3 -0
- package/formly-username-with-domain/README.md +3 -0
- package/generic-assets/README.md +3 -0
- package/generic-autocomplete/README.md +3 -0
- package/generic-button/README.md +3 -0
- package/generic-card/README.md +3 -0
- package/generic-crud-table/README.md +3 -0
- package/generic-dialog/README.md +3 -0
- package/generic-errormessage/README.md +3 -0
- package/generic-formly-fields/README.md +3 -0
- package/generic-loadingspinner/README.md +3 -0
- package/generic-report/README.md +3 -0
- package/generic-report-tabs/README.md +0 -0
- package/generic-search/README.md +3 -0
- package/generic-search-advanced/README.md +3 -0
- package/generic-selector/README.md +3 -0
- package/generic-table/README.md +3 -0
- package/generic-view/README.md +3 -0
- package/header-wrapper/README.md +3 -0
- package/icon-picker/README.md +3 -0
- package/input/README.md +3 -0
- package/input-switch/README.md +3 -0
- package/input-with-icon/README.md +3 -0
- package/label-type/README.md +3 -0
- package/loading-skeletons/README.md +3 -0
- package/odata-query-builder/README.md +3 -0
- package/package.json +264 -0
- package/query-type/README.md +3 -0
- package/radio/README.md +3 -0
- package/repeat/README.md +3 -0
- package/select/README.md +3 -0
- package/sidebar-cards/README.md +3 -0
- package/sidebar-toggles/README.md +3 -0
- package/styles.css +2 -0
- package/tabs/README.md +3 -0
- package/tag-type/README.md +3 -0
- package/text-editor/README.md +3 -0
- package/textarea/README.md +3 -0
- package/types/es.framework-ng.ui.core-attachments.d.ts +38 -0
- package/types/es.framework-ng.ui.core-base-crud.d.ts +74 -0
- package/types/es.framework-ng.ui.core-breadcrumb.d.ts +17 -0
- package/types/es.framework-ng.ui.core-checkbox.d.ts +33 -0
- package/types/es.framework-ng.ui.core-collapsible.d.ts +18 -0
- package/types/es.framework-ng.ui.core-color-picker.d.ts +16 -0
- package/types/es.framework-ng.ui.core-column-settings-popover.d.ts +46 -0
- package/types/es.framework-ng.ui.core-custom-switch.d.ts +19 -0
- package/types/es.framework-ng.ui.core-datepicker.d.ts +23 -0
- package/types/es.framework-ng.ui.core-deactivation-reason.d.ts +32 -0
- package/types/es.framework-ng.ui.core-excel-import.d.ts +69 -0
- package/types/es.framework-ng.ui.core-filter-templates.d.ts +57 -0
- package/types/es.framework-ng.ui.core-form-button.d.ts +17 -0
- package/types/es.framework-ng.ui.core-form-field.d.ts +30 -0
- package/types/es.framework-ng.ui.core-form-template.d.ts +16 -0
- package/types/es.framework-ng.ui.core-formly-avatar-image.d.ts +18 -0
- package/types/es.framework-ng.ui.core-formly-avatar-label.d.ts +36 -0
- package/types/es.framework-ng.ui.core-formly-button-selector.d.ts +35 -0
- package/types/es.framework-ng.ui.core-formly-button.d.ts +20 -0
- package/types/es.framework-ng.ui.core-formly-presets.d.ts +37 -0
- package/types/es.framework-ng.ui.core-formly-prime-icon-picker.d.ts +45 -0
- package/types/es.framework-ng.ui.core-formly-split-button.d.ts +23 -0
- package/types/es.framework-ng.ui.core-formly-ui-all.d.ts +40 -0
- package/types/es.framework-ng.ui.core-formly-ui.d.ts +13 -0
- package/types/es.framework-ng.ui.core-formly-username-with-domain.d.ts +19 -0
- package/types/es.framework-ng.ui.core-generic-assets.d.ts +10 -0
- package/types/es.framework-ng.ui.core-generic-autocomplete.d.ts +41 -0
- package/types/es.framework-ng.ui.core-generic-button.d.ts +39 -0
- package/types/es.framework-ng.ui.core-generic-card.d.ts +93 -0
- package/types/es.framework-ng.ui.core-generic-crud-table.d.ts +293 -0
- package/types/es.framework-ng.ui.core-generic-dialog.d.ts +93 -0
- package/types/es.framework-ng.ui.core-generic-errormessage.d.ts +17 -0
- package/types/es.framework-ng.ui.core-generic-formly-fields.d.ts +26 -0
- package/types/es.framework-ng.ui.core-generic-loadingspinner.d.ts +16 -0
- package/types/es.framework-ng.ui.core-generic-report-tabs.d.ts +21 -0
- package/types/es.framework-ng.ui.core-generic-report.d.ts +121 -0
- package/types/es.framework-ng.ui.core-generic-search-advanced.d.ts +305 -0
- package/types/es.framework-ng.ui.core-generic-search.d.ts +36 -0
- package/types/es.framework-ng.ui.core-generic-selector.d.ts +99 -0
- package/types/es.framework-ng.ui.core-generic-table.d.ts +84 -0
- package/types/es.framework-ng.ui.core-generic-view.d.ts +97 -0
- package/types/es.framework-ng.ui.core-header-wrapper.d.ts +15 -0
- package/types/es.framework-ng.ui.core-icon-picker.d.ts +30 -0
- package/types/es.framework-ng.ui.core-input-switch.d.ts +16 -0
- package/types/es.framework-ng.ui.core-input-with-icon.d.ts +17 -0
- package/types/es.framework-ng.ui.core-input.d.ts +47 -0
- package/types/es.framework-ng.ui.core-label-type.d.ts +38 -0
- package/types/es.framework-ng.ui.core-loading-skeletons.d.ts +17 -0
- package/types/es.framework-ng.ui.core-odata-query-builder.d.ts +87 -0
- package/types/es.framework-ng.ui.core-query-type.d.ts +37 -0
- package/types/es.framework-ng.ui.core-radio.d.ts +38 -0
- package/types/es.framework-ng.ui.core-repeat.d.ts +31 -0
- package/types/es.framework-ng.ui.core-select.d.ts +42 -0
- package/types/es.framework-ng.ui.core-sidebar-cards.d.ts +25 -0
- package/types/es.framework-ng.ui.core-sidebar-toggles.d.ts +25 -0
- package/types/es.framework-ng.ui.core-tabs.d.ts +17 -0
- package/types/es.framework-ng.ui.core-tag-type.d.ts +34 -0
- package/types/es.framework-ng.ui.core-text-editor.d.ts +16 -0
- package/types/es.framework-ng.ui.core-textarea.d.ts +36 -0
- package/types/es.framework-ng.ui.core-wrappers.d.ts +36 -0
- package/types/es.framework-ng.ui.core.d.ts +8 -0
- 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
|