@es.framework/ng.ui.core 2.0.61 → 2.0.63

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.
@@ -3,6 +3,10 @@ import { NgModule, Component } from '@angular/core';
3
3
  import { CommonModule } from '@angular/common';
4
4
  import { TranslatePipe } from '@es.framework/ng.core/pipes';
5
5
  import { FieldArrayType, FormlyField } from '@ngx-formly/core';
6
+ import * as i1 from '@angular/cdk/drag-drop';
7
+ import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
8
+ import * as i2 from '@angular/forms';
9
+ import { FormsModule } from '@angular/forms';
6
10
 
7
11
  class RepeatModule {
8
12
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RepeatModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -18,141 +22,356 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
18
22
 
19
23
  class RepeatTypeComponent extends FieldArrayType {
20
24
  cdr;
21
- // Return columns of the first row (for table headers)
22
- getFirstRowColumns() {
23
- return this.field.fieldGroup?.[0]?.fieldGroup ?? [];
24
- }
25
- // ✅ NEW VERSION OF YOUR FUNCTION
26
- // getFirstRowColumns(): FormlyFieldConfig[] {
27
- // const group = this.field.fieldGroup?.[0]?.fieldGroup ?? [];
28
- // return group.map(f => ({
29
- // ...f,
30
- // type: 'label-type', // 🔥 force label-type here
31
- // readonly: true,
32
- // disabled: true,
33
- // }));
34
- // }
25
+ searchQuery = '';
26
+ currentPage = 0;
27
+ pageSize = 10;
35
28
  constructor(cdr) {
36
29
  super();
37
30
  this.cdr = cdr;
38
31
  }
39
32
  ngOnInit() {
40
- // Subscribe to each FormArray changes
41
- // if (this.formControl instanceof FormArray) {
42
33
  this.field.formControl.valueChanges.subscribe(() => {
43
- // Force Angular to detect changes
44
34
  this.field.fieldGroup = [...this.field.fieldGroup ?? []];
45
35
  this.cdr.detectChanges();
46
36
  });
47
- // }
37
+ }
38
+ getFirstRowColumns() {
39
+ return this.field.fieldGroup?.[0]?.fieldGroup ?? [];
40
+ }
41
+ // Row visibility / Delete Protection logic
42
+ canDelete(index) {
43
+ const isFirst = index === 0 && this.currentPage === 0;
44
+ const isSecond = index === 1 && this.currentPage === 0;
45
+ if (isFirst && !this.field.props?.['canDeleteFirstRow'])
46
+ return false;
47
+ if (isSecond && !this.field.props?.['canDeleteSecondRow'])
48
+ return false;
49
+ return true;
50
+ }
51
+ // Pagination Logic
52
+ totalPages() {
53
+ return Math.ceil((this.field.fieldGroup?.length || 0) / this.pageSize) || 1;
54
+ }
55
+ displayedRows() {
56
+ const start = this.currentPage * this.pageSize;
57
+ return (this.field.fieldGroup || []).slice(start, start + this.pageSize);
58
+ }
59
+ nextPage() { if (this.currentPage < this.totalPages() - 1)
60
+ this.currentPage++; }
61
+ prevPage() { if (this.currentPage > 0)
62
+ this.currentPage--; }
63
+ // Totals Logic
64
+ // inside RepeatTypeComponent class
65
+ getFooterConfig(columnKey) {
66
+ // Access footer configuration defined in the field props
67
+ const footerConfigs = this.field.props?.['footerConfig'];
68
+ if (!footerConfigs)
69
+ return null;
70
+ // Find config for this specific key (e.g., { key: 'amount', label: 'Sum', type: 'sum' })
71
+ return footerConfigs.find((c) => c.key === columnKey);
72
+ }
73
+ calculateTotal(key, type = 'sum') {
74
+ const values = (this.formControl.value || [])
75
+ .map((v) => parseFloat(v[key]))
76
+ .filter((v) => !isNaN(v));
77
+ if (values.length === 0)
78
+ return '0.00';
79
+ let result = 0;
80
+ switch (type) {
81
+ case 'avg':
82
+ result = values.reduce((a, b) => a + b, 0) / values.length;
83
+ break;
84
+ case 'count':
85
+ return values.length.toString();
86
+ case 'sum':
87
+ default:
88
+ result = values.reduce((a, b) => a + b, 0);
89
+ break;
90
+ }
91
+ // Format as enterprise currency/number
92
+ return result.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
93
+ }
94
+ // Search Logic
95
+ onSearch() {
96
+ if (!this.searchQuery)
97
+ return;
98
+ const index = (this.field.fieldGroup || []).findIndex(row => JSON.stringify(row.model).toLowerCase().includes(this.searchQuery.toLowerCase()));
99
+ if (index !== -1) {
100
+ this.currentPage = Math.floor(index / this.pageSize);
101
+ // Optional: Scroll to matched row after a small timeout
102
+ setTimeout(() => {
103
+ document.getElementById(`row-${index % this.pageSize}`)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
104
+ }, 100);
105
+ }
106
+ }
107
+ isMatched(row) {
108
+ if (!this.searchQuery)
109
+ return false;
110
+ return JSON.stringify(row.model).toLowerCase().includes(this.searchQuery.toLowerCase());
111
+ }
112
+ onDrop(event) {
113
+ moveItemInArray(this.field.fieldGroup, event.previousIndex, event.currentIndex);
114
+ const formArray = this.formControl;
115
+ const temp = formArray.at(event.previousIndex);
116
+ formArray.removeAt(event.previousIndex);
117
+ formArray.insert(event.currentIndex, temp);
118
+ this.field.fieldGroup = [...this.field.fieldGroup ?? []];
119
+ this.cdr.detectChanges();
48
120
  }
49
121
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RepeatTypeComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
50
122
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: RepeatTypeComponent, isStandalone: true, selector: "formly-repeat-section", usesInheritance: true, ngImport: i0, template: `
51
- <div class="overflow-auto bg-gray-100 border border-gray-300">
52
- @if ((field.fieldGroup ?? []).length > 0) {
53
- <table class="table-auto w-full border border-gray-300">
123
+ <div class="dx-grid-container shadow-md border border-[#c1c1c1] bg-white text-[#333] rounded-sm overflow-hidden">
124
+
125
+ <div class="bg-gradient-to-b from-[#fdfdfd] to-[#ececec] border-b border-[#c1c1c1] px-3 py-1.5 flex items-center justify-between gap-4">
126
+ <span class="text-[11px] font-bold uppercase tracking-tight text-[#555]">{{ field.props?.label || '' }}</span>
127
+
128
+ @if (field.props?.['enableSearch']) {
129
+ <div class="relative flex items-center">
130
+ <i class="pi pi-search absolute left-2 text-[10px] text-gray-400"></i>
131
+ <input
132
+ [(ngModel)]="searchQuery"
133
+ (input)="onSearch()"
134
+ [placeholder]="'Search' | translate"
135
+ class="pl-7 pr-2 py-1 text-[11px] border border-[#ccc] rounded-sm focus:border-[#0078d7] outline-none w-48 transition-all"
136
+ />
137
+ </div>
138
+ }
139
+ </div>
140
+
141
+ <div class="overflow-x-auto min-h-[100px]">
142
+ <table class="w-full border-collapse table-auto">
54
143
  <thead>
55
- <tr class="bg-gray-100 text-gray-700 text-sm">
56
- <th class="w-8"></th>
144
+ <tr class="bg-[#f2f2f2] border-b border-[#d1d1d1]">
145
+ <th class="sticky right-0 z-10 w-12 border-r border-[#d1d1d1] bg-[#f2f2f2]"></th>
146
+
147
+ <th class="sticky right-12 z-10 w-12 border-r border-[#d1d1d1] bg-[#f2f2f2]"></th>
57
148
  @for (col of getFirstRowColumns(); track col) {
58
- <th class="text-center border">@if (!col.hide) {<span>{{ (col.props?.label ?? '') | translate }}</span>}</th>
59
- <!-- <th class="text-center border"><formly-field [field]="col"></formly-field></th> -->
149
+ <!-- @if (!col.hide) { -->
150
+
151
+ <th [class.hidden]="col.className?.includes('hidden') || col.hide" style="text-align: initial;" class="px-3 py-2 text-[12px] font-semibold text-[#444] border-r border-[#d1d1d1] bg-gradient-to-b from-[#fdfdfd] to-[#f2f2f2] whitespace-nowrap" >
152
+ {{ (col.props?.label ?? '') | translate }}
153
+ </th>
154
+
60
155
  }
61
- <th class="w-10"></th>
62
156
  </tr>
63
157
  </thead>
64
- <tbody>
65
- @for (row of field.fieldGroup; track row; let rowIndex = $index) {
66
- <tr class="border-t">
67
- <td class="bg-[#fffbea] text-center text-gray-400 px-2"><i class="pi pi-bars"></i></td>
158
+
159
+ <tbody cdkDropList (cdkDropListDropped)="onDrop($event)" [cdkDropListData]="field.fieldGroup">
160
+ @for (row of displayedRows(); track row; let rowIndex = $index) {
161
+ <tr cdkDrag
162
+ [id]="'row-' + rowIndex"
163
+ [class.bg-yellow-50]="isMatched(row)"
164
+ class="dx-row border-b border-[#e0e0e0] hover:bg-[#eff4ff] transition-colors bg-white">
165
+
166
+ <div *cdkDragPlaceholder class="bg-blue-50 border-2 border-dashed border-blue-200 h-10"></div>
167
+
168
+ <td class="sticky right-0 z-10 text-center border-r border-[#e0e0e0] bg-[#f6f6f6] py-0 px-2 cursor-move" cdkDragHandle>
169
+ <div class="flex items-center gap-1">
170
+ <i class="pi pi-bars text-[10px] text-gray-300"></i>
171
+ <span class="text-[10px] text-gray-400 font-mono">{{ (currentPage * pageSize) + rowIndex + 1 }}</span>
172
+ </div>
173
+ </td>
174
+
175
+ <td class="sticky right-12 z-10 px-2 py-0 text-center bg-[#fcfcfc] w-12 border-r border-[#e0e0e0]">
176
+ @if (canDelete(rowIndex) && !(field.props?.['delete']?.hidden ?? false)) {
177
+ <button type="button" (click)="remove(rowIndex)" class="p-2 text-gray-400 hover:text-red-600 transition-colors">
178
+ <i class="pi pi-trash text-[13px]"></i>
179
+ </button>
180
+ }
181
+ </td>
182
+
68
183
  @for (col of row.fieldGroup; track col) {
69
- <td class="bg-[#fffbea]">
70
- <formly-field [field]="col"></formly-field>
71
- </td>
184
+ <!-- @if (!col.hide) { -->
185
+
186
+ <td [class.hidden]="col.className?.includes('hidden') || col.hide" class="p-0 border-r border-[#e0e0e0] align-middle min-w-[150px]">
187
+ <div class="dx-field-cell">
188
+ <formly-field [field]="col"></formly-field>
189
+ </div>
190
+ </td>
191
+
72
192
  }
73
- <td class="text-center px-2 bg-[#fffbea]">
74
- @if ((rowIndex !== 0 || field.props?.['canDeleteFirstRow']) && !(field.props?.['delete']?.hidden ?? false)) {
75
- <button type="button" (click)="remove(rowIndex)" class="text-red-600 hover:text-red-800">
76
- <i class="pi pi-trash"></i>
77
- </button>
78
- }
79
- </td>
193
+
194
+
80
195
  </tr>
81
196
  }
82
197
  </tbody>
83
- </table>
198
+
199
+
200
+ @if (field.props?.['showFooterTotals']) {
201
+ <tfoot>
202
+ <tr class="bg-[#f9f9f9] border-t-2 border-[#d1d1d1] font-bold text-[12px]">
203
+ <td class="border-r border-[#e0e0e0] bg-[#eee]"></td>
204
+
205
+ @for (col of getFirstRowColumns(); track col) {
206
+ <!-- @if (!col.hide) { -->
207
+ <td [class.hidden]="col.className?.includes('hidden') || col.hide" class="px-3 py-1 border-r border-[#e0e0e0] text-[#333]">
208
+ @if (getFooterConfig(col.key); as cfg) {
209
+ <div class="flex flex-col leading-tight">
210
+ <span class="text-[8px] text-gray-400 uppercase tracking-tighter">
211
+ {{ cfg.label | translate }}
212
+ </span>
213
+ <span class="text-[#005a9e]">
214
+ {{ calculateTotal(col.key, cfg.type) }}
215
+ </span>
216
+ </div>
217
+ }
218
+ </td>
219
+
84
220
  }
85
-
86
- @if (!(field.props?.['add']?.hidden ?? false)) {
87
- <div class="flex justify-end">
88
- <button
89
- type="button"
90
- class="inline-flex items-center gap-1 px-4 py-2 bg-primary-600 text-white text-sm font-medium hover:bg-primary-500 transition-colors duration-200 shadow-sm"
91
- (click)="add()"
92
- >
93
- <span class="text-lg">+</span>
94
- </button>
221
+ <td class="bg-[#eee]"></td>
222
+ </tr>
223
+ </tfoot>
224
+ }
225
+ </table>
226
+ </div>
227
+
228
+ <div class="p-2 border-t border-[#c1c1c1] bg-[#f3f3f3] flex items-center justify-between">
229
+ <div class="flex items-center gap-2">
230
+ @if (!(field.props?.['add']?.hidden ?? false)) {
231
+ <button type="button" (click)="add()" class="flex items-center gap-2 px-4 py-1.5 rounded-sm border border-primary-500 bg-primary-600 text-white text-[11px] font-medium hover:bg-primary-700 shadow-sm transition-all">
232
+ <i class="pi pi-plus text-[10px]"></i>
233
+ <span>{{ 'Add' | translate }}</span>
234
+ </button>
235
+ }
236
+ </div>
237
+
238
+ <div class="flex items-center gap-1 text-[11px] text-gray-600">
239
+ <button (click)="nextPage()" [disabled]="currentPage >= totalPages() - 1" class="px-2 py-1 border border-gray-300 bg-white disabled:opacity-50"><i class="pi pi-chevron-right text-[9px]"></i></button>
240
+ <span class="px-2"> {{ currentPage + 1 }} - {{ totalPages() }}</span>
241
+ <button (click)="prevPage()" [disabled]="currentPage === 0" class="px-2 py-1 border border-gray-300 bg-white disabled:opacity-50"><i class="pi pi-chevron-left text-[9px]"></i></button>
242
+
243
+ </div>
95
244
  </div>
96
- }
97
245
  </div>
98
- `, isInline: true, dependencies: [{ kind: "component", type: FormlyField, selector: "formly-field", inputs: ["field"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
246
+ `, isInline: true, styles: [".right-12{right:38px}\n"], dependencies: [{ kind: "component", type: FormlyField, selector: "formly-field", inputs: ["field"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i1.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
99
247
  }
100
248
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RepeatTypeComponent, decorators: [{
101
249
  type: Component,
102
- args: [{
103
- selector: 'formly-repeat-section',
104
- standalone: true,
105
- imports: [FormlyField, TranslatePipe],
106
- template: `
107
- <div class="overflow-auto bg-gray-100 border border-gray-300">
108
- @if ((field.fieldGroup ?? []).length > 0) {
109
- <table class="table-auto w-full border border-gray-300">
250
+ args: [{ selector: 'formly-repeat-section', standalone: true, imports: [FormlyField, TranslatePipe, DragDropModule, FormsModule], template: `
251
+ <div class="dx-grid-container shadow-md border border-[#c1c1c1] bg-white text-[#333] rounded-sm overflow-hidden">
252
+
253
+ <div class="bg-gradient-to-b from-[#fdfdfd] to-[#ececec] border-b border-[#c1c1c1] px-3 py-1.5 flex items-center justify-between gap-4">
254
+ <span class="text-[11px] font-bold uppercase tracking-tight text-[#555]">{{ field.props?.label || '' }}</span>
255
+
256
+ @if (field.props?.['enableSearch']) {
257
+ <div class="relative flex items-center">
258
+ <i class="pi pi-search absolute left-2 text-[10px] text-gray-400"></i>
259
+ <input
260
+ [(ngModel)]="searchQuery"
261
+ (input)="onSearch()"
262
+ [placeholder]="'Search' | translate"
263
+ class="pl-7 pr-2 py-1 text-[11px] border border-[#ccc] rounded-sm focus:border-[#0078d7] outline-none w-48 transition-all"
264
+ />
265
+ </div>
266
+ }
267
+ </div>
268
+
269
+ <div class="overflow-x-auto min-h-[100px]">
270
+ <table class="w-full border-collapse table-auto">
110
271
  <thead>
111
- <tr class="bg-gray-100 text-gray-700 text-sm">
112
- <th class="w-8"></th>
272
+ <tr class="bg-[#f2f2f2] border-b border-[#d1d1d1]">
273
+ <th class="sticky right-0 z-10 w-12 border-r border-[#d1d1d1] bg-[#f2f2f2]"></th>
274
+
275
+ <th class="sticky right-12 z-10 w-12 border-r border-[#d1d1d1] bg-[#f2f2f2]"></th>
113
276
  @for (col of getFirstRowColumns(); track col) {
114
- <th class="text-center border">@if (!col.hide) {<span>{{ (col.props?.label ?? '') | translate }}</span>}</th>
115
- <!-- <th class="text-center border"><formly-field [field]="col"></formly-field></th> -->
277
+ <!-- @if (!col.hide) { -->
278
+
279
+ <th [class.hidden]="col.className?.includes('hidden') || col.hide" style="text-align: initial;" class="px-3 py-2 text-[12px] font-semibold text-[#444] border-r border-[#d1d1d1] bg-gradient-to-b from-[#fdfdfd] to-[#f2f2f2] whitespace-nowrap" >
280
+ {{ (col.props?.label ?? '') | translate }}
281
+ </th>
282
+
116
283
  }
117
- <th class="w-10"></th>
118
284
  </tr>
119
285
  </thead>
120
- <tbody>
121
- @for (row of field.fieldGroup; track row; let rowIndex = $index) {
122
- <tr class="border-t">
123
- <td class="bg-[#fffbea] text-center text-gray-400 px-2"><i class="pi pi-bars"></i></td>
286
+
287
+ <tbody cdkDropList (cdkDropListDropped)="onDrop($event)" [cdkDropListData]="field.fieldGroup">
288
+ @for (row of displayedRows(); track row; let rowIndex = $index) {
289
+ <tr cdkDrag
290
+ [id]="'row-' + rowIndex"
291
+ [class.bg-yellow-50]="isMatched(row)"
292
+ class="dx-row border-b border-[#e0e0e0] hover:bg-[#eff4ff] transition-colors bg-white">
293
+
294
+ <div *cdkDragPlaceholder class="bg-blue-50 border-2 border-dashed border-blue-200 h-10"></div>
295
+
296
+ <td class="sticky right-0 z-10 text-center border-r border-[#e0e0e0] bg-[#f6f6f6] py-0 px-2 cursor-move" cdkDragHandle>
297
+ <div class="flex items-center gap-1">
298
+ <i class="pi pi-bars text-[10px] text-gray-300"></i>
299
+ <span class="text-[10px] text-gray-400 font-mono">{{ (currentPage * pageSize) + rowIndex + 1 }}</span>
300
+ </div>
301
+ </td>
302
+
303
+ <td class="sticky right-12 z-10 px-2 py-0 text-center bg-[#fcfcfc] w-12 border-r border-[#e0e0e0]">
304
+ @if (canDelete(rowIndex) && !(field.props?.['delete']?.hidden ?? false)) {
305
+ <button type="button" (click)="remove(rowIndex)" class="p-2 text-gray-400 hover:text-red-600 transition-colors">
306
+ <i class="pi pi-trash text-[13px]"></i>
307
+ </button>
308
+ }
309
+ </td>
310
+
124
311
  @for (col of row.fieldGroup; track col) {
125
- <td class="bg-[#fffbea]">
126
- <formly-field [field]="col"></formly-field>
127
- </td>
312
+ <!-- @if (!col.hide) { -->
313
+
314
+ <td [class.hidden]="col.className?.includes('hidden') || col.hide" class="p-0 border-r border-[#e0e0e0] align-middle min-w-[150px]">
315
+ <div class="dx-field-cell">
316
+ <formly-field [field]="col"></formly-field>
317
+ </div>
318
+ </td>
319
+
128
320
  }
129
- <td class="text-center px-2 bg-[#fffbea]">
130
- @if ((rowIndex !== 0 || field.props?.['canDeleteFirstRow']) && !(field.props?.['delete']?.hidden ?? false)) {
131
- <button type="button" (click)="remove(rowIndex)" class="text-red-600 hover:text-red-800">
132
- <i class="pi pi-trash"></i>
133
- </button>
134
- }
135
- </td>
321
+
322
+
136
323
  </tr>
137
324
  }
138
325
  </tbody>
139
- </table>
326
+
327
+
328
+ @if (field.props?.['showFooterTotals']) {
329
+ <tfoot>
330
+ <tr class="bg-[#f9f9f9] border-t-2 border-[#d1d1d1] font-bold text-[12px]">
331
+ <td class="border-r border-[#e0e0e0] bg-[#eee]"></td>
332
+
333
+ @for (col of getFirstRowColumns(); track col) {
334
+ <!-- @if (!col.hide) { -->
335
+ <td [class.hidden]="col.className?.includes('hidden') || col.hide" class="px-3 py-1 border-r border-[#e0e0e0] text-[#333]">
336
+ @if (getFooterConfig(col.key); as cfg) {
337
+ <div class="flex flex-col leading-tight">
338
+ <span class="text-[8px] text-gray-400 uppercase tracking-tighter">
339
+ {{ cfg.label | translate }}
340
+ </span>
341
+ <span class="text-[#005a9e]">
342
+ {{ calculateTotal(col.key, cfg.type) }}
343
+ </span>
344
+ </div>
345
+ }
346
+ </td>
347
+
140
348
  }
141
-
142
- @if (!(field.props?.['add']?.hidden ?? false)) {
143
- <div class="flex justify-end">
144
- <button
145
- type="button"
146
- class="inline-flex items-center gap-1 px-4 py-2 bg-primary-600 text-white text-sm font-medium hover:bg-primary-500 transition-colors duration-200 shadow-sm"
147
- (click)="add()"
148
- >
149
- <span class="text-lg">+</span>
150
- </button>
349
+ <td class="bg-[#eee]"></td>
350
+ </tr>
351
+ </tfoot>
352
+ }
353
+ </table>
354
+ </div>
355
+
356
+ <div class="p-2 border-t border-[#c1c1c1] bg-[#f3f3f3] flex items-center justify-between">
357
+ <div class="flex items-center gap-2">
358
+ @if (!(field.props?.['add']?.hidden ?? false)) {
359
+ <button type="button" (click)="add()" class="flex items-center gap-2 px-4 py-1.5 rounded-sm border border-primary-500 bg-primary-600 text-white text-[11px] font-medium hover:bg-primary-700 shadow-sm transition-all">
360
+ <i class="pi pi-plus text-[10px]"></i>
361
+ <span>{{ 'Add' | translate }}</span>
362
+ </button>
363
+ }
364
+ </div>
365
+
366
+ <div class="flex items-center gap-1 text-[11px] text-gray-600">
367
+ <button (click)="nextPage()" [disabled]="currentPage >= totalPages() - 1" class="px-2 py-1 border border-gray-300 bg-white disabled:opacity-50"><i class="pi pi-chevron-right text-[9px]"></i></button>
368
+ <span class="px-2"> {{ currentPage + 1 }} - {{ totalPages() }}</span>
369
+ <button (click)="prevPage()" [disabled]="currentPage === 0" class="px-2 py-1 border border-gray-300 bg-white disabled:opacity-50"><i class="pi pi-chevron-left text-[9px]"></i></button>
370
+
371
+ </div>
151
372
  </div>
152
- }
153
373
  </div>
154
- `,
155
- }]
374
+ `, styles: [".right-12{right:38px}\n"] }]
156
375
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }] });
157
376
 
158
377
  class RepeatTableViewType extends FieldArrayType {
@@ -1 +1 @@
1
- {"version":3,"file":"es.framework-ng.ui.core-repeat.mjs","sources":["../../../../libs/ng.ui.core/repeat/src/lib/repeat-module.ts","../../../../libs/ng.ui.core/repeat/src/lib/repeat-section.type.ts","../../../../libs/ng.ui.core/repeat/src/lib/repeat-table-view.type.ts","../../../../libs/ng.ui.core/repeat/src/es.framework-ng.ui.core-repeat.ts"],"sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n@NgModule({\n imports: [CommonModule],\n})\nexport class RepeatModule {}\n","import { TranslatePipe } from '@es.framework/ng.core/pipes';\nimport { ChangeDetectorRef, Component, OnInit } from '@angular/core';\nimport { FieldArrayType, FormlyField, FormlyFieldConfig } from '@ngx-formly/core';\n\nimport { FormArray } from '@angular/forms';\n\n@Component({\n selector: 'formly-repeat-section',\n standalone: true,\n imports: [FormlyField, TranslatePipe],\n template: `\n <div class=\"overflow-auto bg-gray-100 border border-gray-300\">\n @if ((field.fieldGroup ?? []).length > 0) {\n <table class=\"table-auto w-full border border-gray-300\">\n <thead>\n <tr class=\"bg-gray-100 text-gray-700 text-sm\">\n <th class=\"w-8\"></th>\n @for (col of getFirstRowColumns(); track col) {\n <th class=\"text-center border\">@if (!col.hide) {<span>{{ (col.props?.label ?? '') | translate }}</span>}</th>\n <!-- <th class=\"text-center border\"><formly-field [field]=\"col\"></formly-field></th> -->\n }\n <th class=\"w-10\"></th>\n </tr>\n </thead>\n <tbody>\n @for (row of field.fieldGroup; track row; let rowIndex = $index) {\n <tr class=\"border-t\">\n <td class=\"bg-[#fffbea] text-center text-gray-400 px-2\"><i class=\"pi pi-bars\"></i></td>\n @for (col of row.fieldGroup; track col) {\n <td class=\"bg-[#fffbea]\">\n <formly-field [field]=\"col\"></formly-field>\n </td>\n }\n <td class=\"text-center px-2 bg-[#fffbea]\">\n @if ((rowIndex !== 0 || field.props?.['canDeleteFirstRow']) && !(field.props?.['delete']?.hidden ?? false)) {\n <button type=\"button\" (click)=\"remove(rowIndex)\" class=\"text-red-600 hover:text-red-800\">\n <i class=\"pi pi-trash\"></i>\n </button>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n }\n \n @if (!(field.props?.['add']?.hidden ?? false)) {\n <div class=\"flex justify-end\">\n <button\n type=\"button\"\n class=\"inline-flex items-center gap-1 px-4 py-2 bg-primary-600 text-white text-sm font-medium hover:bg-primary-500 transition-colors duration-200 shadow-sm\"\n (click)=\"add()\"\n >\n <span class=\"text-lg\">+</span>\n </button>\n </div>\n }\n </div>\n `,\n})\nexport class RepeatTypeComponent extends FieldArrayType implements OnInit {\n // Return columns of the first row (for table headers)\n getFirstRowColumns() {\n return this.field.fieldGroup?.[0]?.fieldGroup ?? [];\n }\n // ✅ NEW VERSION OF YOUR FUNCTION\n // getFirstRowColumns(): FormlyFieldConfig[] {\n // const group = this.field.fieldGroup?.[0]?.fieldGroup ?? [];\n\n // return group.map(f => ({\n // ...f,\n // type: 'label-type', // 🔥 force label-type here\n // readonly: true,\n // disabled: true,\n // }));\n // }\n constructor(private cdr: ChangeDetectorRef) {\n super();\n }\n ngOnInit() {\n\n // Subscribe to each FormArray changes\n // if (this.formControl instanceof FormArray) {\n this.field.formControl.valueChanges.subscribe(() => {\n // Force Angular to detect changes\n\n this.field.fieldGroup = [...this.field.fieldGroup ?? []];\n this.cdr.detectChanges();\n });\n // }\n }\n}\n","\nimport { Component } from '@angular/core';\nimport { FieldArrayType } from '@ngx-formly/core';\nimport { TranslatePipe } from '@es.framework/ng.core/pipes';\n\n@Component({\n selector: 'formly-repeat-table-view',\n template: `\n <div class=\"mb-0\">\n \n <!-- <label class=\"block text-lg font-semibold mb-3\">\n {{ field.props?.label }}\n </label> -->\n \n <div class=\"overflow-x-auto rounded-xl border\">\n <table class=\"min-w-full bg-white border-collapse\">\n \n <thead class=\"bg-gray-100\">\n <tr>\n <!-- text-left -->\n @for (col of columns; track col) {\n <th\n class=\"px-4 py-3 text-right font-medium border-b\"\n >\n {{ col.label | translate }}\n </th>\n }\n </tr>\n </thead>\n \n <tbody>\n @for (row of rows; track row; let i = $index) {\n <tr\n class=\"border-b hover:bg-gray-50\">\n @for (col of columns; track col) {\n <td\n class=\"px-4 py-2\">\n {{ row[col.key] }}\n </td>\n }\n </tr>\n }\n \n @if (!rows || rows.length === 0) {\n <tr>\n <td [attr.colspan]=\"columns.length\"\n class=\"text-center py-6 text-gray-400\">\n No data\n </td>\n </tr>\n }\n \n </tbody>\n \n </table>\n </div>\n \n </div>\n \n `,\n imports: [TranslatePipe],\n})\nexport class RepeatTableViewType extends FieldArrayType {\n\n get columns() {\n const group = (this.field?.fieldArray as any)?.fieldGroup || [];\n return group.map((f :any)=> ({\n key: f.key as string,\n label: f.props?.label || f.key\n }));\n }\n\n get rows() {\n return this.model || [];\n }\n\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;MAMa,YAAY,CAAA;uGAAZ,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,QAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,YAAY,YAFb,YAAY,CAAA,EAAA,CAAA;AAEX,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,YAAY,YAFb,YAAY,CAAA,EAAA,CAAA;;2FAEX,YAAY,EAAA,UAAA,EAAA,CAAA;kBAHxB,QAAQ;AAAC,YAAA,IAAA,EAAA,CAAA;oBACR,OAAO,EAAE,CAAC,YAAY,CAAC;AACxB,iBAAA;;;ACuDK,MAAO,mBAAoB,SAAQ,cAAc,CAAA;AAgBjC,IAAA,GAAA;;IAdpB,kBAAkB,GAAA;AAChB,QAAA,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,EAAE,UAAU,IAAI,EAAE;IACrD;;;;;;;;;;;AAYA,IAAA,WAAA,CAAoB,GAAsB,EAAA;AACxC,QAAA,KAAK,EAAE;QADW,IAAA,CAAA,GAAG,GAAH,GAAG;IAEvB;IACA,QAAQ,GAAA;;;QAIJ,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,SAAS,CAAC,MAAK;;AAGjD,YAAA,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC;AACxD,YAAA,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE;AAC1B,QAAA,CAAC,CAAC;;IAEN;uGA9BW,mBAAmB,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,CAAA,iBAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAnB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,mBAAmB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,uBAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAlDpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgDP,EAAA,QAAA,EAAA,IAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAjDO,WAAW,uEAAE,aAAa,EAAA,IAAA,EAAA,WAAA,EAAA,CAAA,EAAA,CAAA;;2FAmDzB,mBAAmB,EAAA,UAAA,EAAA,CAAA;kBAtD/B,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,uBAAuB;AACjC,oBAAA,UAAU,EAAE,IAAI;AAChB,oBAAA,OAAO,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC;AACrC,oBAAA,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDP,IAAA,CAAA;AACJ,iBAAA;;;ACGK,MAAO,mBAAoB,SAAQ,cAAc,CAAA;AAErD,IAAA,IAAI,OAAO,GAAA;QACT,MAAM,KAAK,GAAI,IAAI,CAAC,KAAK,EAAE,UAAkB,EAAE,UAAU,IAAI,EAAE;QAC/D,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAM,MAAK;YAC3B,GAAG,EAAE,CAAC,CAAC,GAAa;YACpB,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC;AAC5B,SAAA,CAAC,CAAC;IACL;AAEA,IAAA,IAAI,IAAI,GAAA;AACN,QAAA,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE;IACzB;uGAZW,mBAAmB,EAAA,IAAA,EAAA,IAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAnB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,mBAAmB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,0BAAA,EAAA,eAAA,EAAA,IAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAvDpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EACS,aAAa,EAAA,IAAA,EAAA,WAAA,EAAA,CAAA,EAAA,CAAA;;2FAEZ,mBAAmB,EAAA,UAAA,EAAA,CAAA;kBAzD/B,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,0BAA0B;AACpC,oBAAA,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDT,EAAA,CAAA;oBACD,OAAO,EAAE,CAAC,aAAa,CAAC;AACzB,iBAAA;;;AC7DD;;AAEG;;;;"}
1
+ {"version":3,"file":"es.framework-ng.ui.core-repeat.mjs","sources":["../../../../libs/ng.ui.core/repeat/src/lib/repeat-module.ts","../../../../libs/ng.ui.core/repeat/src/lib/repeat-section.type.ts","../../../../libs/ng.ui.core/repeat/src/lib/repeat-table-view.type.ts","../../../../libs/ng.ui.core/repeat/src/es.framework-ng.ui.core-repeat.ts"],"sourcesContent":["import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n@NgModule({\n imports: [CommonModule],\n})\nexport class RepeatModule {}\n","import { TranslatePipe } from '@es.framework/ng.core/pipes';\nimport { ChangeDetectorRef, Component, OnInit, signal, computed } from '@angular/core';\nimport { FieldArrayType, FormlyField, FormlyFieldConfig } from '@ngx-formly/core';\nimport { DragDropModule, moveItemInArray, CdkDragDrop } from '@angular/cdk/drag-drop';\nimport { FormsModule } from '@angular/forms';\n\n@Component({\n selector: 'formly-repeat-section',\n standalone: true,\n imports: [FormlyField, TranslatePipe, DragDropModule, FormsModule],\n template: `\n <div class=\"dx-grid-container shadow-md border border-[#c1c1c1] bg-white text-[#333] rounded-sm overflow-hidden\">\n \n <div class=\"bg-gradient-to-b from-[#fdfdfd] to-[#ececec] border-b border-[#c1c1c1] px-3 py-1.5 flex items-center justify-between gap-4\">\n <span class=\"text-[11px] font-bold uppercase tracking-tight text-[#555]\">{{ field.props?.label || '' }}</span>\n \n @if (field.props?.['enableSearch']) {\n <div class=\"relative flex items-center\">\n <i class=\"pi pi-search absolute left-2 text-[10px] text-gray-400\"></i>\n <input \n [(ngModel)]=\"searchQuery\" \n (input)=\"onSearch()\"\n [placeholder]=\"'Search' | translate\" \n class=\"pl-7 pr-2 py-1 text-[11px] border border-[#ccc] rounded-sm focus:border-[#0078d7] outline-none w-48 transition-all\"\n />\n </div>\n }\n </div>\n\n <div class=\"overflow-x-auto min-h-[100px]\">\n <table class=\"w-full border-collapse table-auto\">\n <thead>\n <tr class=\"bg-[#f2f2f2] border-b border-[#d1d1d1]\">\n <th class=\"sticky right-0 z-10 w-12 border-r border-[#d1d1d1] bg-[#f2f2f2]\"></th>\n \n <th class=\"sticky right-12 z-10 w-12 border-r border-[#d1d1d1] bg-[#f2f2f2]\"></th>\n @for (col of getFirstRowColumns(); track col) {\n <!-- @if (!col.hide) { -->\n \n <th [class.hidden]=\"col.className?.includes('hidden') || col.hide\" style=\"text-align: initial;\" class=\"px-3 py-2 text-[12px] font-semibold text-[#444] border-r border-[#d1d1d1] bg-gradient-to-b from-[#fdfdfd] to-[#f2f2f2] whitespace-nowrap\" >\n {{ (col.props?.label ?? '') | translate }}\n </th>\n \n }\n </tr>\n </thead>\n \n <tbody cdkDropList (cdkDropListDropped)=\"onDrop($event)\" [cdkDropListData]=\"field.fieldGroup\">\n @for (row of displayedRows(); track row; let rowIndex = $index) {\n <tr cdkDrag \n [id]=\"'row-' + rowIndex\"\n [class.bg-yellow-50]=\"isMatched(row)\"\n class=\"dx-row border-b border-[#e0e0e0] hover:bg-[#eff4ff] transition-colors bg-white\">\n \n <div *cdkDragPlaceholder class=\"bg-blue-50 border-2 border-dashed border-blue-200 h-10\"></div>\n\n <td class=\"sticky right-0 z-10 text-center border-r border-[#e0e0e0] bg-[#f6f6f6] py-0 px-2 cursor-move\" cdkDragHandle>\n <div class=\"flex items-center gap-1\">\n <i class=\"pi pi-bars text-[10px] text-gray-300\"></i>\n <span class=\"text-[10px] text-gray-400 font-mono\">{{ (currentPage * pageSize) + rowIndex + 1 }}</span>\n </div>\n </td>\n\n <td class=\"sticky right-12 z-10 px-2 py-0 text-center bg-[#fcfcfc] w-12 border-r border-[#e0e0e0]\">\n @if (canDelete(rowIndex) && !(field.props?.['delete']?.hidden ?? false)) {\n <button type=\"button\" (click)=\"remove(rowIndex)\" class=\"p-2 text-gray-400 hover:text-red-600 transition-colors\">\n <i class=\"pi pi-trash text-[13px]\"></i>\n </button>\n }\n </td>\n \n @for (col of row.fieldGroup; track col) {\n <!-- @if (!col.hide) { -->\n \n <td [class.hidden]=\"col.className?.includes('hidden') || col.hide\" class=\"p-0 border-r border-[#e0e0e0] align-middle min-w-[150px]\">\n <div class=\"dx-field-cell\">\n <formly-field [field]=\"col\"></formly-field>\n </div>\n </td>\n \n }\n\n \n </tr>\n }\n </tbody>\n\n \n@if (field.props?.['showFooterTotals']) {\n <tfoot>\n <tr class=\"bg-[#f9f9f9] border-t-2 border-[#d1d1d1] font-bold text-[12px]\">\n <td class=\"border-r border-[#e0e0e0] bg-[#eee]\"></td>\n \n @for (col of getFirstRowColumns(); track col) {\n <!-- @if (!col.hide) { -->\n <td [class.hidden]=\"col.className?.includes('hidden') || col.hide\" class=\"px-3 py-1 border-r border-[#e0e0e0] text-[#333]\">\n @if (getFooterConfig(col.key); as cfg) {\n <div class=\"flex flex-col leading-tight\">\n <span class=\"text-[8px] text-gray-400 uppercase tracking-tighter\">\n {{ cfg.label | translate }}\n </span>\n <span class=\"text-[#005a9e]\">\n {{ calculateTotal(col.key, cfg.type) }}\n </span>\n </div>\n }\n </td>\n \n }\n <td class=\"bg-[#eee]\"></td>\n </tr>\n </tfoot>\n}\n </table>\n </div>\n\n <div class=\"p-2 border-t border-[#c1c1c1] bg-[#f3f3f3] flex items-center justify-between\">\n <div class=\"flex items-center gap-2\">\n @if (!(field.props?.['add']?.hidden ?? false)) {\n <button type=\"button\" (click)=\"add()\" class=\"flex items-center gap-2 px-4 py-1.5 rounded-sm border border-primary-500 bg-primary-600 text-white text-[11px] font-medium hover:bg-primary-700 shadow-sm transition-all\">\n <i class=\"pi pi-plus text-[10px]\"></i>\n <span>{{ 'Add' | translate }}</span>\n </button>\n }\n </div>\n\n <div class=\"flex items-center gap-1 text-[11px] text-gray-600\">\n <button (click)=\"nextPage()\" [disabled]=\"currentPage >= totalPages() - 1\" class=\"px-2 py-1 border border-gray-300 bg-white disabled:opacity-50\"><i class=\"pi pi-chevron-right text-[9px]\"></i></button>\n <span class=\"px-2\"> {{ currentPage + 1 }} - {{ totalPages() }}</span>\n <button (click)=\"prevPage()\" [disabled]=\"currentPage === 0\" class=\"px-2 py-1 border border-gray-300 bg-white disabled:opacity-50\"><i class=\"pi pi-chevron-left text-[9px]\"></i></button>\n \n </div>\n </div>\n </div>\n `,\n styles: [`\n :host ::ng-deep {\n .dx-field-cell .form-control, .dx-field-cell input, .dx-field-cell select {\n @apply w-full bg-transparent border-none px-3 py-2 text-[13px] outline-none;\n border-radius: 0; box-shadow: none !important;\n }\n .dx-field-cell:focus-within { @apply bg-white ring-1 ring-inset ring-[#0078d7]; box-shadow: inset 0 0 0 1px #0078d7; }\n .dx-row:nth-child(even) { background-color: #fafafa; }\n .cdk-drag-preview { @apply shadow-2xl border border-blue-200 bg-white opacity-90; display: table; width: 100%; }\n }\n .right-12{\n right: 38px\n }\n `]\n})\nexport class RepeatTypeComponent extends FieldArrayType implements OnInit {\n searchQuery = '';\n currentPage = 0;\n pageSize = 10;\n\n constructor(private cdr: ChangeDetectorRef) { super(); }\n\n ngOnInit() {\n this.field.formControl.valueChanges.subscribe(() => {\n this.field.fieldGroup = [...this.field.fieldGroup ?? []];\n this.cdr.detectChanges();\n });\n }\n\n getFirstRowColumns() {\n return this.field.fieldGroup?.[0]?.fieldGroup ?? [];\n }\n\n // Row visibility / Delete Protection logic\n canDelete(index: number): boolean {\n const isFirst = index === 0 && this.currentPage === 0;\n const isSecond = index === 1 && this.currentPage === 0;\n \n if (isFirst && !this.field.props?.['canDeleteFirstRow']) return false;\n if (isSecond && !this.field.props?.['canDeleteSecondRow']) return false;\n \n return true;\n }\n\n // Pagination Logic\n totalPages() {\n return Math.ceil((this.field.fieldGroup?.length || 0) / this.pageSize) || 1;\n }\n\n displayedRows() {\n const start = this.currentPage * this.pageSize;\n return (this.field.fieldGroup || []).slice(start, start + this.pageSize);\n }\n\n nextPage() { if (this.currentPage < this.totalPages() - 1) this.currentPage++; }\n prevPage() { if (this.currentPage > 0) this.currentPage--; }\n\n // Totals Logic\n // inside RepeatTypeComponent class\n\ngetFooterConfig(columnKey: any) {\n // Access footer configuration defined in the field props\n const footerConfigs = this.field.props?.['footerConfig'];\n if (!footerConfigs) return null;\n \n // Find config for this specific key (e.g., { key: 'amount', label: 'Sum', type: 'sum' })\n return footerConfigs.find((c: any) => c.key === columnKey);\n}\n\ncalculateTotal(key: any, type: 'sum' | 'avg' | 'count' = 'sum'): string {\n const values = (this.formControl.value || [])\n .map((v: any) => parseFloat(v[key]))\n .filter((v: number) => !isNaN(v));\n\n if (values.length === 0) return '0.00';\n\n let result = 0;\n switch (type) {\n case 'avg':\n result = values.reduce((a:any, b:any) => a + b, 0) / values.length;\n break;\n case 'count':\n return values.length.toString();\n case 'sum':\n default:\n result = values.reduce((a:any, b:any) => a + b, 0);\n break;\n }\n\n // Format as enterprise currency/number\n return result.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n}\n\n // Search Logic\n onSearch() {\n if (!this.searchQuery) return;\n const index = (this.field.fieldGroup || []).findIndex(row => \n JSON.stringify(row.model).toLowerCase().includes(this.searchQuery.toLowerCase())\n );\n \n if (index !== -1) {\n this.currentPage = Math.floor(index / this.pageSize);\n // Optional: Scroll to matched row after a small timeout\n setTimeout(() => {\n document.getElementById(`row-${index % this.pageSize}`)?.scrollIntoView({ behavior: 'smooth', block: 'center' });\n }, 100);\n }\n }\n\n isMatched(row: FormlyFieldConfig): boolean {\n if (!this.searchQuery) return false;\n return JSON.stringify(row.model).toLowerCase().includes(this.searchQuery.toLowerCase());\n }\n\n onDrop(event: CdkDragDrop<any[]> | any) {\n moveItemInArray(this.field.fieldGroup as any[], event.previousIndex, event.currentIndex);\n const formArray = this.formControl as any;\n const temp = formArray.at(event.previousIndex);\n formArray.removeAt(event.previousIndex);\n formArray.insert(event.currentIndex, temp);\n this.field.fieldGroup = [...this.field.fieldGroup ?? []];\n this.cdr.detectChanges();\n }\n}","\nimport { Component } from '@angular/core';\nimport { FieldArrayType } from '@ngx-formly/core';\nimport { TranslatePipe } from '@es.framework/ng.core/pipes';\n\n@Component({\n selector: 'formly-repeat-table-view',\n template: `\n <div class=\"mb-0\">\n \n <!-- <label class=\"block text-lg font-semibold mb-3\">\n {{ field.props?.label }}\n </label> -->\n \n <div class=\"overflow-x-auto rounded-xl border\">\n <table class=\"min-w-full bg-white border-collapse\">\n \n <thead class=\"bg-gray-100\">\n <tr>\n <!-- text-left -->\n @for (col of columns; track col) {\n <th\n class=\"px-4 py-3 text-right font-medium border-b\"\n >\n {{ col.label | translate }}\n </th>\n }\n </tr>\n </thead>\n \n <tbody>\n @for (row of rows; track row; let i = $index) {\n <tr\n class=\"border-b hover:bg-gray-50\">\n @for (col of columns; track col) {\n <td\n class=\"px-4 py-2\">\n {{ row[col.key] }}\n </td>\n }\n </tr>\n }\n \n @if (!rows || rows.length === 0) {\n <tr>\n <td [attr.colspan]=\"columns.length\"\n class=\"text-center py-6 text-gray-400\">\n No data\n </td>\n </tr>\n }\n \n </tbody>\n \n </table>\n </div>\n \n </div>\n \n `,\n imports: [TranslatePipe],\n})\nexport class RepeatTableViewType extends FieldArrayType {\n\n get columns() {\n const group = (this.field?.fieldArray as any)?.fieldGroup || [];\n return group.map((f :any)=> ({\n key: f.key as string,\n label: f.props?.label || f.key\n }));\n }\n\n get rows() {\n return this.model || [];\n }\n\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@es.framework/ng.ui.core",
3
- "version": "2.0.61",
3
+ "version": "2.0.63",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^20.1.0",
6
6
  "@angular/core": "^20.1.0"