tailjng 0.0.55 → 0.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/cli/settings/components-list.js +8 -0
  2. package/cli/settings/header-generator.js +1 -1
  3. package/fesm2022/tailjng.mjs.map +1 -1
  4. package/lib/interfaces/crud/crud.interface.d.ts +5 -2
  5. package/lib/interfaces/crud/filter.interface.d.ts +4 -0
  6. package/package.json +1 -1
  7. package/src/lib/components/card/card-complete/complete-card.component.html +104 -0
  8. package/src/lib/components/card/card-complete/complete-card.component.scss +35 -0
  9. package/src/lib/components/card/card-complete/complete-card.component.ts +652 -0
  10. package/src/lib/components/coach-mark/coach-mark.component.html +36 -0
  11. package/src/lib/components/coach-mark/coach-mark.component.scss +20 -0
  12. package/src/lib/components/coach-mark/coach-mark.component.ts +45 -0
  13. package/src/lib/components/coach-mark/coach-mark.directive.ts +256 -0
  14. package/src/lib/components/filter/filter-complete/complete-filter.component.html +194 -178
  15. package/src/lib/components/filter/filter-complete/complete-filter.component.ts +11 -1
  16. package/src/lib/components/form/form-validation/validation-form.component.html +1 -1
  17. package/src/lib/components/input/input/input.component.html +1 -1
  18. package/src/lib/components/input/input-file/file-input.component.html +3 -3
  19. package/src/lib/components/input/input-textarea/textarea-input.component.html +23 -19
  20. package/src/lib/components/select/select-dropdown/dropdown-select.component.html +5 -4
  21. package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +3 -0
  22. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +2 -1
  23. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +3 -1
  24. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +7 -1
@@ -1,195 +1,211 @@
1
- <div class="flex flex-col md:flex-row-reverse justify-between items-center mb-4 gap-3 select-none">
2
- <div class="flex items-center gap-3 md:w-auto w-full">
3
-
4
- <!-- Search -->
5
- <div class="relative w-full">
6
-
7
- <input
8
- type="text"
9
- [(ngModel)]="searchQuery"
10
- [placeholder]="searchPlaceholder"
11
- class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 pr-16 focus:outline-none focus:ring-2 focus:ring-primary"
12
- />
13
-
14
- <div class="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center gap-2">
15
-
16
- @if (isLoadingSearch) {
17
- <lucide-icon [name]="iconsService.icons.loading" size="18" class="text-gray-400 animate-spin"></lucide-icon>
18
- }
19
-
20
- @if (searchQuery && !isLoadingSearch) {
21
- <button
22
- (click)="clearSearch()"
23
- class="pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
24
- aria-label="Limpiar búsqueda"
25
- >
26
- <lucide-icon [name]="iconsService.icons.close" size="16"></lucide-icon>
27
- </button>
28
- }
29
-
30
- @if (!isLoadingSearch && !searchQuery) {
31
- <lucide-icon [name]="iconsService.icons.search" size="16" class="text-gray-400"></lucide-icon>
32
- }
33
-
34
- </div>
1
+ <div
2
+ class="flex flex-col md:flex-row-reverse justify-between items-center mb-4 gap-3 select-none"
3
+ >
4
+ <div class="flex items-center gap-3 md:w-auto w-full">
5
+ <!-- Search -->
6
+ @if (isSearch) {
7
+ <div class="relative w-full">
8
+ <input
9
+ type="text"
10
+ [(ngModel)]="searchQuery"
11
+ [placeholder]="searchPlaceholder"
12
+ class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
13
+ />
14
+
15
+ <div
16
+ class="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center gap-2"
17
+ >
18
+ @if (isLoadingSearch) {
19
+ <lucide-icon
20
+ [name]="iconsService.icons.loading"
21
+ size="18"
22
+ class="text-gray-400 animate-spin"
23
+ ></lucide-icon>
24
+ }
25
+
26
+ @if (searchQuery && !isLoadingSearch) {
27
+ <button
28
+ (click)="clearSearch()"
29
+ class="pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
30
+ aria-label="Limpiar búsqueda"
31
+ >
32
+ <lucide-icon
33
+ [name]="iconsService.icons.close"
34
+ size="16"
35
+ ></lucide-icon>
36
+ </button>
37
+ }
38
+
39
+ @if (!isLoadingSearch && !searchQuery) {
40
+ <lucide-icon
41
+ [name]="iconsService.icons.search"
42
+ size="16"
43
+ class="text-gray-400"
44
+ ></lucide-icon>
45
+ }
35
46
  </div>
47
+ </div>
48
+ }
49
+
50
+ <!-- Elements per page -->
51
+ @if (isItemsPerPage) {
52
+ <div class="min-w-[75px]">
53
+ <JDropdownSelect
54
+ [isLoading]="isLoadingPerPage"
55
+ [(ngModel)]="itemsPerPage"
56
+ (selectionChange)="onItemsPerPageChange()"
57
+ placeholder="Mostrar"
58
+ title="Mostrar elementos"
59
+ [options]="itemsPerPageOptions"
60
+ />
61
+ </div>
62
+ }
63
+
64
+ <!-- Show columns -->
65
+ @if (columns.length > 0 && isShowColumns) {
66
+ <div>
67
+ <JMultiTableSelect
68
+ type="multi-table"
69
+ title="Seleccionar columnas"
70
+ [columns]="visibleColumns"
71
+ />
72
+ </div>
73
+ }
74
+ </div>
36
75
 
37
- <!-- Elements per page -->
38
- @if (isItemsPerPage) {
39
- <div class="min-w-[75px]">
40
- <JDropdownSelect
41
- [isLoading]="isLoadingPerPage"
42
- [(ngModel)]="itemsPerPage"
43
- (selectionChange)="onItemsPerPageChange()"
44
- placeholder="Mostrar"
45
- title="Mostrar elementos" [options]="itemsPerPageOptions"
76
+ <div
77
+ class="flex gap-3 text-sm w-full md:w-auto md:justify-center justify-between"
78
+ >
79
+ <div class="flex flex-row flex-wrap gap-2">
80
+ @for (button of filtersButton; track $index) {
81
+ @if (getIsVisible(button)) {
82
+ <JButton
83
+ [icon]="button.icon"
84
+ [iconSize]="22"
85
+ [iconChange]="button.iconChange"
86
+ [isChangeIcon]="getIsChangeIcon(button)"
87
+ [isLoading]="getIsLoading(button)"
88
+ [disabled]="getDisabled(button)"
89
+ [tooltip]="getTooltip(button)"
90
+ [tooltipPosition]="button.tooltipPosition ?? 'top'"
91
+ (clicked)="filterBottomClick(button)"
92
+ [classes]="button.classes ?? ''"
93
+ [ngClasses]="{ 'min-w-auto p-1! pl-2! pr-2!': true }"
94
+ />
95
+
96
+ @if (button.type === "upload") {
97
+ <input
98
+ #hiddenFileInput
99
+ type="file"
100
+ accept=".xlsx,.xls"
101
+ (change)="onCustomFileSelected($event, button)"
102
+ style="display: none"
46
103
  />
47
- </div>
104
+ }
48
105
  }
49
-
50
- <!-- Show columns -->
51
- @if (columns.length > 0 && isShowColumns) {
52
- <div>
53
- <JMultiTableSelect
54
- type="multi-table"
55
- title="Seleccionar columnas"
56
- [columns]="visibleColumns"
57
- />
58
- </div>
59
- }
60
-
61
- </div>
62
-
63
- <div class="flex gap-3 text-sm w-full md:w-auto md:justify-center justify-between " >
64
-
65
- <div class="flex flex-row flex-wrap gap-2">
66
- @for (button of filtersButton; track $index) {
67
- @if (getIsVisible(button)) {
68
- <JButton
69
- [icon]="button.icon"
70
- [iconSize]="22"
71
- [iconChange]="button.iconChange"
72
- [isChangeIcon]="getIsChangeIcon(button)"
73
- [isLoading]="isButtonLoading(button)"
74
- [disabled]="getDisabled(button)"
75
- [tooltip]="getTooltip(button)"
76
- [tooltipPosition]="button.tooltipPosition ?? 'top'"
77
- (clicked)="filterBottomClick(button)"
78
- [classes]="button.classes ?? ''"
79
- [ngClasses]="{'min-w-auto p-1! pl-2! pr-2!' : true}"
80
- />
81
-
82
- @if (button.type === 'upload') {
83
- <input
84
- #hiddenFileInput
85
- type="file"
86
- accept=".xlsx,.xls"
87
- (change)="onCustomFileSelected($event, button)"
88
- style="display: none"
89
- />
90
- }
91
- }
92
- }
93
-
94
- @if (checked) {
95
- <JSwitchCheckbox onKeyPress
96
- [title]="titleChecked"
97
- [isChecked]="isChecked"
98
- (click)="checkActiveInactive.emit(isChecked)"
99
- [isLoading]="isLoadingChecked"
100
- />
101
- }
102
- </div>
103
-
106
+ }
107
+
108
+ @if (checked) {
109
+ <JSwitchCheckbox
110
+ onKeyPress
111
+ [title]="titleChecked"
112
+ [isChecked]="isChecked"
113
+ (click)="checkActiveInactive.emit(isChecked)"
114
+ [isLoading]="isLoadingChecked"
115
+ />
116
+ }
104
117
  </div>
118
+ </div>
105
119
  </div>
106
120
 
107
-
108
121
  <!-- Content Filters -->
109
- <div class="select-none">
110
- <JDialog
111
- [openModal]="dialogOpen"
112
- (closeModal)="dialogOpen = false"
113
- [dialogTemplate]="templateDialog"
114
- position="leftCenter"
115
- title="Filtros"
116
- [width]="220"
117
- height="auto"
118
- [overlay]="false"
119
- [draggable]="true"
120
- />
122
+ <div class="select-none">
123
+ <JDialog
124
+ [openModal]="dialogOpen"
125
+ (closeModal)="dialogOpen = false"
126
+ [dialogTemplate]="templateDialog"
127
+ position="leftCenter"
128
+ title="Filtros"
129
+ [width]="220"
130
+ height="auto"
131
+ [overlay]="false"
132
+ [draggable]="true"
133
+ />
121
134
  </div>
122
135
 
123
136
  <ng-template #templateDialog>
124
- <div class="m-0 pt-1">
125
- @if (filtersSelect.length > 0) {
126
- <div class="flex flex-col gap-2">
127
- @for (filter of filtersSelect; track $index) {
128
- @if (filter.isVisible ?? true) {
137
+ <div class="m-0 pt-1">
138
+ @if (filtersSelect.length > 0) {
139
+ <div class="flex flex-col gap-2">
140
+ @for (filter of filtersSelect; track $index) {
141
+ @if (filter.isVisible ?? true) {
142
+ @if (filter.type === "dropdown") {
143
+ <div>
144
+ <JDropdownSelect
145
+ [type]="'dropdown'"
146
+ [(ngModel)]="filter.selected"
147
+ (selectionChange)="
148
+ filter.onSelected ? filter.onSelected($event) : null
149
+ "
150
+ [optionLabel]="filter.optionLabel ?? ''"
151
+ [optionValue]="filter.optionValue ?? ''"
152
+ [labelSeparator]="filter.labelSeparator ?? ''"
153
+ [placeholder]="filter.placeholder ?? ''"
154
+ [showClear]="filter.showClear ?? false"
155
+ [options]="filter.options"
156
+ [sort]="filter.sort ?? 'ASC'"
157
+ [showAllOption]="filter.showAllOption ?? false"
158
+ />
159
+ </div>
160
+ }
129
161
 
130
- @if (filter.type === 'dropdown') {
131
- <div>
132
- <JDropdownSelect
133
- [type]="'dropdown'"
134
- [(ngModel)]="filter.selected"
135
- (selectionChange)="filter.onSelected ? filter.onSelected($event) : null"
136
- [optionLabel]="filter.optionLabel ?? ''"
137
- [optionValue]="filter.optionValue ?? ''"
138
- [placeholder]="filter.placeholder ?? ''"
139
- [showClear]="filter.showClear ?? false"
140
- [options]="filter.options"
141
- [sort]="filter.sort ?? 'ASC'"
142
- [showAllOption]="filter.showAllOption ?? false"
143
- />
144
- </div>
145
- }
146
-
147
- <!-- Searchable -->
148
- @if (filter.type === 'searchable') {
149
- <div>
150
- <JDropdownSelect
151
- [type]="'searchable'"
152
- [(ngModel)]="filter.selected"
153
- (selectionChange)="filter.onSelected?.($event)"
154
- [endpoint]="filter.endpoint"
155
- [optionLabel]="filter.optionLabel"
156
- [optionValue]="filter.optionValue"
157
- [placeholder]="filter.placeholder ?? ''"
158
- [showClear]="filter.showClear ?? false"
159
- [loadOnInit]="filter.loadOnInit ?? false"
160
- [isSearch]="filter.isSearch ?? true"
161
- [searchFields]="filter.searchFields || []"
162
- [defaultFilters]="filter.defaultFilters || []"
163
- [sort]="filter.sort ?? 'ASC'"
164
- [showAllOption]="filter.showAllOption ?? false"
165
- />
166
- </div>
167
- }
168
-
169
- @if (filter.type === 'multi-table') {
170
- <div>
171
- <JMultiTableSelect
172
- [(ngModel)]="filter.selected"
173
- (selectionChange)="filter.onSelected ? filter.onSelected($event) : null"
174
- [columns]="filter.columns"
175
- [btnText]="filter.btnText ?? ''"
176
- [isFilterSelect]="true"
177
- />
178
- </div>
179
- }
180
- }
181
- }
182
- </div>
183
-
184
- } @else {
185
- <div class="text-center text-sm text-black dark:text-white pt-5 pb-5">
186
- <p>No hay filtros disponibles</p>
187
- </div>
162
+ <!-- Searchable -->
163
+ @if (filter.type === "searchable") {
164
+ <div>
165
+ <JDropdownSelect
166
+ [type]="'searchable'"
167
+ [(ngModel)]="filter.selected"
168
+ (selectionChange)="filter.onSelected?.($event)"
169
+ [endpoint]="filter.endpoint"
170
+ [optionLabel]="filter.optionLabel"
171
+ [optionValue]="filter.optionValue"
172
+ [labelSeparator]="filter.labelSeparator ?? ''"
173
+ [placeholder]="filter.placeholder ?? ''"
174
+ [showClear]="filter.showClear ?? false"
175
+ [loadOnInit]="filter.loadOnInit ?? false"
176
+ [isSearch]="filter.isSearch ?? true"
177
+ [searchFields]="filter.searchFields || []"
178
+ [defaultFilters]="filter.defaultFilters || []"
179
+ [sort]="filter.sort ?? 'ASC'"
180
+ [showAllOption]="filter.showAllOption ?? false"
181
+ />
182
+ </div>
183
+ }
184
+
185
+ @if (filter.type === "multi-table") {
186
+ <div>
187
+ <JMultiTableSelect
188
+ [(ngModel)]="filter.selected"
189
+ (selectionChange)="
190
+ filter.onSelected ? filter.onSelected($event) : null
191
+ "
192
+ [columns]="filter.columns"
193
+ [btnText]="filter.btnText ?? ''"
194
+ [isFilterSelect]="true"
195
+ />
196
+ </div>
197
+ }
198
+ }
188
199
  }
189
- </div>
200
+ </div>
201
+ } @else {
202
+ <div class="text-center text-sm text-black dark:text-white pt-5 pb-5">
203
+ <p>No hay filtros disponibles</p>
204
+ </div>
205
+ }
206
+ </div>
190
207
  </ng-template>
191
208
 
192
-
193
209
  <!-- Content load data -->
194
210
  <!-- <JDialog
195
211
  [openModal]="dialogOpenCharge"
@@ -224,4 +240,4 @@
224
240
 
225
241
  </div>
226
242
  </div>
227
- </ng-template> -->
243
+ </ng-template> -->
@@ -1,4 +1,3 @@
1
-
2
1
  import { Component, EventEmitter, Input, Output, OnDestroy, OnInit, ViewChild, ElementRef } from '@angular/core';
3
2
  import { FormsModule } from '@angular/forms';
4
3
  import { LucideAngularModule } from 'lucide-angular';
@@ -29,6 +28,7 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
29
28
  @Input() searchPlaceholder: string = 'Buscar...';
30
29
 
31
30
  // Inputs
31
+ @Input() isSearch: boolean = true;
32
32
  @Input() params: any;
33
33
  @Input() columns: TableColumn<any>[] = [];
34
34
  @Input() endpoint!: string;
@@ -490,4 +490,14 @@ export class JCompleteFilterComponent implements OnInit, OnDestroy {
490
490
  // Si no se define, por defecto es visible
491
491
  return button.isVisible !== false;
492
492
  }
493
+
494
+ getIsLoading(button: FilterButton): boolean {
495
+ if (button.isLoading === undefined) {
496
+ return false;
497
+ }
498
+ if (typeof button.isLoading === 'function') {
499
+ return button.isLoading();
500
+ }
501
+ return button.isLoading;
502
+ }
493
503
  }
@@ -1,5 +1,5 @@
1
1
  @if (hasErrors) {
2
- <div class="flex flex-col text-red-600 dark:text-red-300 text-sm">
2
+ <div class="flex flex-col text-red-600 dark:text-red-300 text-[12px]">
3
3
  @for (message of errors; track $index) {
4
4
  <span [class]="classes">{{ message }}</span>
5
5
  }
@@ -11,7 +11,7 @@
11
11
  (blur)="onTouched()"
12
12
  [ngClass]="combinedNgClass"
13
13
  [class]="classes"
14
- class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
14
+ class="input w-full h-[40px] bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
15
15
  />
16
16
 
17
17
  @if (value && clearButton) {
@@ -15,13 +15,13 @@
15
15
 
16
16
  <label
17
17
  [for]="id"
18
- class="input block text-sm dark:text-gray-400 w-full h-[40px] pr-1 bg-background dark:bg-dark-background border border-border dark:border-dark-border flex items-center justify-between text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200 cursor-pointer"
18
+ class="input block text-sm w-full h-[40px] pr-1 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground dark:placeholder:text-dark-muted-foreground rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed flex items-center justify-between transition duration-200 cursor-pointer"
19
19
  >
20
- <div class="truncate" [ngClass]="{ 'opacity-50' : !innerValue?.name }">
20
+ <div class="truncate" [ngClass]="{ 'opacity-80' : !innerValue?.name }">
21
21
  @if (innerValue?.name) {
22
22
  <span>{{ innerValue?.name }}</span>
23
23
  } @else {
24
- <span class="flex items-center gap-2">
24
+ <span class="flex items-center gap-2 text-muted-foreground dark:text-dark-muted-foreground">
25
25
  <lucide-icon [name]="iconsService.icons.upload" [size]="15" />
26
26
  Seleccionar archivo...
27
27
  </span>
@@ -1,21 +1,25 @@
1
1
  <div class="relative w-full flex">
2
- <textarea
3
- [id]="id"
4
- [name]="name ?? ''"
5
- [placeholder]="placeholder"
6
- [value]="value"
7
- (input)="onInput($event)"
8
- [required]="required"
9
- [disabled]="disabled"
10
- (blur)="onTouched()"
11
- [ngClass]="combinedNgClass"
12
- [class]="classes"
13
- class="input w-full h-[70px] min-h-[70px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary transition duration-200"
14
- ></textarea>
2
+ <textarea
3
+ [id]="id"
4
+ [name]="name ?? ''"
5
+ [placeholder]="placeholder"
6
+ [value]="value"
7
+ (input)="onInput($event)"
8
+ [required]="required"
9
+ [disabled]="disabled"
10
+ (blur)="onTouched()"
11
+ [ngClass]="combinedNgClass"
12
+ [class]="classes"
13
+ class="input w-full h-[70px] min-h-[70px] pr-10 bg-background dark:bg-dark-background border border-border dark:border-dark-border text-black dark:text-white placeholder:text-muted-foreground/70 dark:placeholder:text-dark-muted-foreground/70 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-dark-primary transition duration-200 resize-y disabled:opacity-60 disabled:cursor-not-allowed"
14
+ ></textarea>
15
15
 
16
- @if (value && clearButton) {
17
- <button type="button" class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer" (click)="clearInput()">
18
- <lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
19
- </button>
20
- }
21
- </div>
16
+ @if (value && clearButton) {
17
+ <button
18
+ type="button"
19
+ class="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 text-gray-400 hover:text-gray-500 pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
20
+ (click)="clearInput()"
21
+ >
22
+ <lucide-icon [name]="iconsService.icons.close" class="w-4 h-4" />
23
+ </button>
24
+ }
25
+ </div>
@@ -4,12 +4,13 @@
4
4
  type="button"
5
5
  [disabled]="disabled || isLoading"
6
6
  (click)="toggleColumnSelector()"
7
- class="flex w-full h-[40px] items-center justify-between px-3 py-2 text-sm bg-background dark:bg-dark-background border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary select-none"
7
+ [class]="'flex w-full h-[40px] items-center justify-between px-3 py-2 text-sm bg-background dark:bg-dark-background border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary select-none' + ' ' + classes"
8
8
  [ngClass]="{
9
- 'opacity-50 cursor-not-allowed pointer-events-none': disabled || isLoading
9
+ 'opacity-50 cursor-not-allowed pointer-events-none': disabled || isLoading,
10
+ ngClass: true
10
11
  }"
11
12
  >
12
- <span class="truncate text-black dark:text-white" [ngClass]="{'opacity-50' : selectedValue === null}">{{selectedLabel}}</span>
13
+ <span class="truncate text-black dark:text-white" [ngClass]="{'opacity-70 text-muted-foreground dark:text-dark-muted-foreground' : selectedValue === null}">{{selectedLabel}}</span>
13
14
 
14
15
  <div class="flex items-center">
15
16
  @if (showClear && selectedValue !== null) {
@@ -32,7 +33,7 @@
32
33
  <!-- Dropdown positioned outside the flow -->
33
34
  @if (isColumnSelectorOpen) {
34
35
  <div @modalTransition
35
- class="absolute z-[100] min-w-[250px] mt-1 bg-background dark:bg-dark-background rounded-lg shadow-lg border border-border border-dark-border"
36
+ [class]="'absolute z-[100] min-w-[250px] mt-1 bg-background dark:bg-dark-background rounded-lg shadow-lg border border-border border-dark-border' + ' ' + classes"
36
37
  [style.width.px]="dropdownWidth"
37
38
  [style.top.px]="dropdownTop"
38
39
  [style.left.px]="dropdownLeft"
@@ -55,6 +55,9 @@ export class JDropdownSelectComponent implements ControlValueAccessor, AfterView
55
55
 
56
56
  @Input() isSearch = true;
57
57
  @Input() isFilterSelect = false;
58
+
59
+ @Input() classes: string = '';
60
+ @Input() ngClass: { [key: string]: boolean } = {};
58
61
 
59
62
  @Output() selectionChange = new EventEmitter<any>();
60
63
  @Output() fullData = new EventEmitter<any[]>();
@@ -1,3 +1,4 @@
1
+
1
2
  <div class="relative w-full h-full">
2
3
  <div class="w-auto" #selectButton>
3
4
  <button
@@ -7,7 +8,7 @@
7
8
  class="flex w-full h-[40px] items-center justify-between px-3 py-2 text-sm bg-background dark:bg-dark-background border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary select-none"
8
9
  [ngClass]="{ 'opacity-50 cursor-not-allowed pointer-events-none': disabled || isLoading }"
9
10
  >
10
- <span class="truncate text-black dark:text-white" [ngClass]="{ 'opacity-50': selectedValues.length === 0 }">
11
+ <span class="truncate text-black dark:text-white" [ngClass]="{ 'opacity-70 text-muted-foreground dark:text-dark-muted-foreground': selectedValues.length === 0 }">
11
12
  {{ displayLabel }}
12
13
  </span>
13
14
 
@@ -1,6 +1,6 @@
1
1
  <div class="w-full mb-3">
2
2
 
3
- @if (isSearch) {
3
+ @if (isFilter) {
4
4
  <JFilter
5
5
  [params]="params"
6
6
  [columns]="columns"
@@ -8,6 +8,8 @@
8
8
  [mainEndpoint]="mainEndpoint"
9
9
  [data]="data"
10
10
  [(searchQuery)]="searchQuery"
11
+ [isSearch]="isSearch"
12
+ [isShowColumns]="isEyeColumn"
11
13
  (search)="onSearch()"
12
14
  [searchPlaceholder]="searchPlaceholder"
13
15
  [isItemsPerPage]="isItemsPerPage"
@@ -65,7 +65,9 @@ export class JCompleteCrudTableComponent implements OnInit {
65
65
  @Input() isNumbering: boolean = true;
66
66
  @Input() defaultFilters: { [key: string]: any } = {};
67
67
  @Input() isPaginator = true;
68
+ @Input() isFilter = true;
68
69
  @Input() isSearch = true;
70
+ @Input() isEyeColumn = true;
69
71
  @Input() hideRowCondition?: (row: any) => boolean
70
72
 
71
73
  data: any[] = [];
@@ -1049,6 +1051,7 @@ export class JCompleteCrudTableComponent implements OnInit {
1049
1051
  };
1050
1052
 
1051
1053
  getRowNgClass(row: any): any {
1054
+
1052
1055
  const base: any = {
1053
1056
  'cursor-pointer': this.hasExpandable(),
1054
1057
  };
@@ -1062,7 +1065,10 @@ export class JCompleteCrudTableComponent implements OnInit {
1062
1065
  value = value?.[key];
1063
1066
  }
1064
1067
 
1065
- const normalizedValue = String(value)?.toUpperCase?.() || '';
1068
+ const normalizedValue =
1069
+ value === null || value === undefined
1070
+ ? 'null'
1071
+ : String(value).toUpperCase();
1066
1072
  const semanticClass = columnWithColor.rowColorCondition?.[normalizedValue];
1067
1073
 
1068
1074
  if (semanticClass && this.rowColorMap[semanticClass]) {