@nlabtech/nlabs-grid 1.1.0

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.
@@ -0,0 +1,1881 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Injectable, signal, effect, input, inject, Component, Input, Directive, linkedSignal, EventEmitter, contentChild, TemplateRef, ElementRef, Output, ContentChildren, ViewChild } from '@angular/core';
3
+ import { map, of, delay } from 'rxjs';
4
+ import * as i3 from '@angular/common';
5
+ import { CommonModule } from '@angular/common';
6
+ import * as i4 from '@angular/forms';
7
+ import { FormsModule } from '@angular/forms';
8
+
9
+ const ODATA_BASE_URL = new InjectionToken('ODATA_BASE_URL');
10
+ /**
11
+ * OData v4 Adapter
12
+ * Implements IDataAdapter for OData protocol
13
+ */
14
+ class ODataAdapter {
15
+ http;
16
+ baseUrl;
17
+ constructor(http, baseUrl) {
18
+ this.http = http;
19
+ this.baseUrl = baseUrl;
20
+ }
21
+ getData(request) {
22
+ const url = `${this.baseUrl}?${this.buildQuery(request)}`;
23
+ console.log('OData Request URL:', url);
24
+ return this.http.get(url).pipe(map(response => {
25
+ console.log('OData Response:', response);
26
+ return {
27
+ data: response.value || response.items || [],
28
+ total: response['@odata.count'] || response.count || 0
29
+ };
30
+ }));
31
+ }
32
+ buildQuery(request) {
33
+ const params = [];
34
+ // Pagination
35
+ if (request.skip !== undefined) {
36
+ params.push(`$skip=${request.skip}`);
37
+ }
38
+ if (request.top !== undefined) {
39
+ params.push(`$top=${request.top}`);
40
+ }
41
+ // Sorting
42
+ if (request.orderBy) {
43
+ params.push(`$orderby=${request.orderBy}`);
44
+ }
45
+ // Filtering
46
+ if (request.filter) {
47
+ params.push(`$filter=${encodeURIComponent(request.filter)}`);
48
+ }
49
+ // Select specific fields
50
+ if (request.select && request.select.length > 0) {
51
+ params.push(`$select=${request.select.join(',')}`);
52
+ }
53
+ // Expand related entities
54
+ if (request.expand && request.expand.length > 0) {
55
+ params.push(`$expand=${request.expand.join(',')}`);
56
+ }
57
+ // Always include count
58
+ params.push('$count=true');
59
+ return params.join('&');
60
+ }
61
+ }
62
+
63
+ /**
64
+ * REST API Adapter
65
+ * Implements IDataAdapter for standard REST endpoints
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const adapter = new RestAdapter(http, 'https://api.example.com/users', {
70
+ * dataKey: 'items',
71
+ * totalKey: 'totalCount',
72
+ * usePagination: 'page'
73
+ * });
74
+ * ```
75
+ */
76
+ class RestAdapter {
77
+ http;
78
+ baseUrl;
79
+ config;
80
+ constructor(http, baseUrl, config) {
81
+ this.http = http;
82
+ this.baseUrl = baseUrl;
83
+ this.config = config;
84
+ }
85
+ getData(request) {
86
+ const url = `${this.baseUrl}?${this.buildQuery(request)}`;
87
+ return this.http.get(url).pipe(map(response => this.mapResponse(response)));
88
+ }
89
+ buildQuery(request) {
90
+ const params = [];
91
+ const cfg = this.config || {};
92
+ // Pagination
93
+ const skip = request.skip ?? 0;
94
+ const top = request.top ?? 10;
95
+ const pageParam = cfg.pageParam || 'page';
96
+ const pageSizeParam = cfg.pageSizeParam || 'pageSize';
97
+ const skipParam = cfg.skipParam || 'skip';
98
+ if (cfg.usePagination === 'page') {
99
+ // Page-based pagination
100
+ const page = top > 0 ? Math.floor(skip / top) + 1 : 1;
101
+ params.push(`${pageParam}=${page}`);
102
+ params.push(`${pageSizeParam}=${top}`);
103
+ }
104
+ else {
105
+ // Skip/take pagination (default)
106
+ params.push(`${skipParam}=${skip}`);
107
+ params.push(`${pageSizeParam}=${top}`);
108
+ }
109
+ // Sorting
110
+ if (request.orderBy) {
111
+ const sortParam = cfg.sortParam || 'sort';
112
+ params.push(`${sortParam}=${request.orderBy}`);
113
+ }
114
+ // Filtering
115
+ if (request.filter) {
116
+ const filterParam = cfg.filterParam || 'filter';
117
+ params.push(`${filterParam}=${encodeURIComponent(request.filter)}`);
118
+ }
119
+ // Select specific fields
120
+ if (request.select && request.select.length > 0) {
121
+ const selectParam = cfg.selectParam || 'fields';
122
+ params.push(`${selectParam}=${request.select.join(',')}`);
123
+ }
124
+ // Global search
125
+ if (request.globalSearch) {
126
+ const searchParam = cfg.searchParam || 'search';
127
+ params.push(`${searchParam}=${encodeURIComponent(request.globalSearch)}`);
128
+ }
129
+ // Extra custom parameters
130
+ if (cfg.extraParams) {
131
+ for (const [key, value] of Object.entries(cfg.extraParams)) {
132
+ if (value !== undefined && value !== null && value !== '') {
133
+ params.push(`${key}=${encodeURIComponent(String(value))}`);
134
+ }
135
+ }
136
+ }
137
+ return params.join('&');
138
+ }
139
+ mapResponse(response) {
140
+ const cfg = this.config || {};
141
+ // Custom mapper function takes priority
142
+ if (cfg.responseMapper) {
143
+ return cfg.responseMapper(response);
144
+ }
145
+ // If response is an array, use it directly
146
+ if (Array.isArray(response)) {
147
+ return {
148
+ data: response,
149
+ total: response.length
150
+ };
151
+ }
152
+ const dataKey = cfg.dataKey || 'data';
153
+ const totalKey = cfg.totalKey || 'total';
154
+ // Extract data - support nested keys like 'result.items'
155
+ let data = this.getNestedValue(response, dataKey) ||
156
+ response.items ||
157
+ response.results ||
158
+ response.records ||
159
+ [];
160
+ // Extract total
161
+ let total = this.getNestedValue(response, totalKey) ??
162
+ response.count ??
163
+ response.totalCount ??
164
+ response.totalRecords ??
165
+ (Array.isArray(data) ? data.length : 0);
166
+ return { data, total };
167
+ }
168
+ getNestedValue(obj, path) {
169
+ return path.split('.').reduce((current, key) => current?.[key], obj);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Grid Data Service
175
+ * Central service for managing grid data operations
176
+ */
177
+ class GridDataService {
178
+ adapter;
179
+ setAdapter(adapter) {
180
+ this.adapter = adapter;
181
+ }
182
+ getData(request) {
183
+ if (!this.adapter) {
184
+ throw new Error('Data adapter not set. Please call setAdapter() first.');
185
+ }
186
+ return this.adapter.getData(request);
187
+ }
188
+ buildFilterString(filters) {
189
+ if (!this.adapter) {
190
+ return '';
191
+ }
192
+ // Build OData-style filter string
193
+ const filterParts = filters.map(filter => {
194
+ const field = filter.field;
195
+ const value = typeof filter.value === 'string' ? `'${filter.value}'` : filter.value;
196
+ switch (filter.matchMode) {
197
+ case 'contains':
198
+ return `contains(${field}, ${value})`;
199
+ case 'startsWith':
200
+ return `startswith(${field}, ${value})`;
201
+ case 'endsWith':
202
+ return `endswith(${field}, ${value})`;
203
+ case 'equals':
204
+ return `${field} eq ${value}`;
205
+ case 'notEquals':
206
+ return `${field} ne ${value}`;
207
+ case 'lt':
208
+ return `${field} lt ${value}`;
209
+ case 'lte':
210
+ return `${field} le ${value}`;
211
+ case 'gt':
212
+ return `${field} gt ${value}`;
213
+ case 'gte':
214
+ return `${field} ge ${value}`;
215
+ default:
216
+ return `${field} eq ${value}`;
217
+ }
218
+ });
219
+ return filterParts.join(' and ');
220
+ }
221
+ buildSortString(sorts) {
222
+ return sorts.map(sort => `${sort.field} ${sort.order}`).join(', ');
223
+ }
224
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
225
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridDataService });
226
+ }
227
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridDataService, decorators: [{
228
+ type: Injectable
229
+ }] });
230
+
231
+ class ThemeService {
232
+ THEME_KEY = 'nlabs-grid-theme';
233
+ // Current theme signal
234
+ theme = signal(this.getInitialTheme(), ...(ngDevMode ? [{ debugName: "theme" }] : []));
235
+ // Computed effective theme (resolves 'auto' to actual theme)
236
+ effectiveTheme = signal('light', ...(ngDevMode ? [{ debugName: "effectiveTheme" }] : []));
237
+ constructor() {
238
+ // Apply theme on change
239
+ effect(() => {
240
+ const theme = this.theme();
241
+ const effective = this.resolveTheme(theme);
242
+ this.effectiveTheme.set(effective);
243
+ this.applyTheme(effective);
244
+ this.saveTheme(theme);
245
+ });
246
+ // Listen to system theme changes
247
+ if (typeof window !== 'undefined') {
248
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
249
+ if (this.theme() === 'auto') {
250
+ this.effectiveTheme.set(e.matches ? 'dark' : 'light');
251
+ this.applyTheme(e.matches ? 'dark' : 'light');
252
+ }
253
+ });
254
+ }
255
+ }
256
+ getInitialTheme() {
257
+ if (typeof window === 'undefined') {
258
+ return 'light';
259
+ }
260
+ const saved = localStorage.getItem(this.THEME_KEY);
261
+ if (saved && ['light', 'dark', 'auto'].includes(saved)) {
262
+ return saved;
263
+ }
264
+ // Default to light instead of auto
265
+ return 'light';
266
+ }
267
+ resolveTheme(theme) {
268
+ if (theme !== 'auto') {
269
+ return theme;
270
+ }
271
+ if (typeof window === 'undefined') {
272
+ return 'light';
273
+ }
274
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
275
+ }
276
+ applyTheme(theme) {
277
+ if (typeof document === 'undefined') {
278
+ return;
279
+ }
280
+ document.documentElement.setAttribute('data-theme', theme);
281
+ document.body.classList.remove('light-theme', 'dark-theme');
282
+ document.body.classList.add(`${theme}-theme`);
283
+ }
284
+ saveTheme(theme) {
285
+ if (typeof localStorage !== 'undefined') {
286
+ localStorage.setItem(this.THEME_KEY, theme);
287
+ }
288
+ }
289
+ setTheme(theme) {
290
+ this.theme.set(theme);
291
+ }
292
+ toggleTheme() {
293
+ const current = this.effectiveTheme();
294
+ this.theme.set(current === 'light' ? 'dark' : 'light');
295
+ }
296
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
297
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ThemeService });
298
+ }
299
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ThemeService, decorators: [{
300
+ type: Injectable
301
+ }], ctorParameters: () => [] });
302
+
303
+ class ThemeSelectorComponent {
304
+ showSelector = input(true, ...(ngDevMode ? [{ debugName: "showSelector" }] : []));
305
+ themeService = inject(ThemeService);
306
+ setTheme(theme) {
307
+ this.themeService.setTheme(theme);
308
+ }
309
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ThemeSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
310
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: ThemeSelectorComponent, isStandalone: true, selector: "theme-selector", inputs: { showSelector: { classPropertyName: "showSelector", publicName: "showSelector", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
311
+ @if (showSelector()) {
312
+ <div class="theme-selector">
313
+ <button
314
+ class="theme-btn"
315
+ [class.active]="themeService.effectiveTheme() === 'light'"
316
+ (click)="setTheme('light')"
317
+ title="Light Mode"
318
+ >
319
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
320
+ <circle cx="12" cy="12" r="5"></circle>
321
+ <line x1="12" y1="1" x2="12" y2="3"></line>
322
+ <line x1="12" y1="21" x2="12" y2="23"></line>
323
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
324
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
325
+ <line x1="1" y1="12" x2="3" y2="12"></line>
326
+ <line x1="21" y1="12" x2="23" y2="12"></line>
327
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
328
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
329
+ </svg>
330
+ </button>
331
+ <button
332
+ class="theme-btn"
333
+ [class.active]="themeService.effectiveTheme() === 'dark'"
334
+ (click)="setTheme('dark')"
335
+ title="Dark Mode"
336
+ >
337
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
338
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
339
+ </svg>
340
+ </button>
341
+ <button
342
+ class="theme-btn"
343
+ [class.active]="themeService.theme() === 'auto'"
344
+ (click)="setTheme('auto')"
345
+ title="Auto (System)"
346
+ >
347
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
348
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
349
+ <line x1="8" y1="21" x2="16" y2="21"></line>
350
+ <line x1="12" y1="17" x2="12" y2="21"></line>
351
+ </svg>
352
+ </button>
353
+ </div>
354
+ }
355
+ `, isInline: true, styles: [".theme-selector{display:inline-flex;gap:4px;padding:4px;background:var(--grid-bg-secondary);border:1px solid var(--grid-border-color);border-radius:8px;box-shadow:0 2px 8px #0000001a}.theme-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;background:transparent;border:none;border-radius:6px;color:var(--grid-text-secondary);cursor:pointer;transition:all .2s ease}.theme-btn:hover{background:var(--grid-bg-hover);color:var(--grid-primary-color)}.theme-btn.active{background:var(--grid-primary-color);color:#fff;box-shadow:0 2px 4px #4096ff4d}.theme-btn svg{width:20px;height:20px}\n"] });
356
+ }
357
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ThemeSelectorComponent, decorators: [{
358
+ type: Component,
359
+ args: [{ selector: 'theme-selector', standalone: true, imports: [], template: `
360
+ @if (showSelector()) {
361
+ <div class="theme-selector">
362
+ <button
363
+ class="theme-btn"
364
+ [class.active]="themeService.effectiveTheme() === 'light'"
365
+ (click)="setTheme('light')"
366
+ title="Light Mode"
367
+ >
368
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
369
+ <circle cx="12" cy="12" r="5"></circle>
370
+ <line x1="12" y1="1" x2="12" y2="3"></line>
371
+ <line x1="12" y1="21" x2="12" y2="23"></line>
372
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
373
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
374
+ <line x1="1" y1="12" x2="3" y2="12"></line>
375
+ <line x1="21" y1="12" x2="23" y2="12"></line>
376
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
377
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
378
+ </svg>
379
+ </button>
380
+ <button
381
+ class="theme-btn"
382
+ [class.active]="themeService.effectiveTheme() === 'dark'"
383
+ (click)="setTheme('dark')"
384
+ title="Dark Mode"
385
+ >
386
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
387
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
388
+ </svg>
389
+ </button>
390
+ <button
391
+ class="theme-btn"
392
+ [class.active]="themeService.theme() === 'auto'"
393
+ (click)="setTheme('auto')"
394
+ title="Auto (System)"
395
+ >
396
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
397
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
398
+ <line x1="8" y1="21" x2="16" y2="21"></line>
399
+ <line x1="12" y1="17" x2="12" y2="21"></line>
400
+ </svg>
401
+ </button>
402
+ </div>
403
+ }
404
+ `, styles: [".theme-selector{display:inline-flex;gap:4px;padding:4px;background:var(--grid-bg-secondary);border:1px solid var(--grid-border-color);border-radius:8px;box-shadow:0 2px 8px #0000001a}.theme-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;background:transparent;border:none;border-radius:6px;color:var(--grid-text-secondary);cursor:pointer;transition:all .2s ease}.theme-btn:hover{background:var(--grid-bg-hover);color:var(--grid-primary-color)}.theme-btn.active{background:var(--grid-primary-color);color:#fff;box-shadow:0 2px 4px #4096ff4d}.theme-btn svg{width:20px;height:20px}\n"] }]
405
+ }], propDecorators: { showSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSelector", required: false }] }] } });
406
+
407
+ class GridColumnCommandTemplateDirective {
408
+ templateRef;
409
+ name = '';
410
+ constructor(templateRef) {
411
+ this.templateRef = templateRef;
412
+ }
413
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridColumnCommandTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
414
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: GridColumnCommandTemplateDirective, isStandalone: true, selector: "[nlabsGridColumnCommandTemplate]", inputs: { name: ["nlabsGridColumnCommandTemplate", "name"] }, ngImport: i0 });
415
+ }
416
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridColumnCommandTemplateDirective, decorators: [{
417
+ type: Directive,
418
+ args: [{
419
+ selector: '[nlabsGridColumnCommandTemplate]',
420
+ standalone: true
421
+ }]
422
+ }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{
423
+ type: Input,
424
+ args: ['nlabsGridColumnCommandTemplate']
425
+ }] } });
426
+
427
+ class GridCaptionCommandTemplateDirective {
428
+ templateRef;
429
+ constructor(templateRef) {
430
+ this.templateRef = templateRef;
431
+ }
432
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridCaptionCommandTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
433
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: GridCaptionCommandTemplateDirective, isStandalone: true, selector: "[nlabsGridCaptionCommandTemplate]", ngImport: i0 });
434
+ }
435
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridCaptionCommandTemplateDirective, decorators: [{
436
+ type: Directive,
437
+ args: [{
438
+ selector: '[nlabsGridCaptionCommandTemplate]',
439
+ standalone: true
440
+ }]
441
+ }], ctorParameters: () => [{ type: i0.TemplateRef }] });
442
+
443
+ class GridFooterTemplateDirective {
444
+ templateRef;
445
+ constructor(templateRef) {
446
+ this.templateRef = templateRef;
447
+ }
448
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridFooterTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
449
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: GridFooterTemplateDirective, isStandalone: true, selector: "[nlabsGridFooterTemplate]", ngImport: i0 });
450
+ }
451
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridFooterTemplateDirective, decorators: [{
452
+ type: Directive,
453
+ args: [{
454
+ selector: '[nlabsGridFooterTemplate]',
455
+ standalone: true
456
+ }]
457
+ }], ctorParameters: () => [{ type: i0.TemplateRef }] });
458
+
459
+ class GridColumnComponent {
460
+ // Basic Properties
461
+ field = input('', ...(ngDevMode ? [{ debugName: "field" }] : []));
462
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : []));
463
+ header = input('', ...(ngDevMode ? [{ debugName: "header" }] : [])); // Alias for title
464
+ // Column Behavior
465
+ sortable = input(true, ...(ngDevMode ? [{ debugName: "sortable" }] : []));
466
+ filterable = input(true, ...(ngDevMode ? [{ debugName: "filterable" }] : []));
467
+ visible = input(true, ...(ngDevMode ? [{ debugName: "visible" }] : []));
468
+ resizable = input(true, ...(ngDevMode ? [{ debugName: "resizable" }] : []));
469
+ frozen = input(false, ...(ngDevMode ? [{ debugName: "frozen" }] : []));
470
+ locked = input(false, ...(ngDevMode ? [{ debugName: "locked" }] : [])); // Cannot be reordered
471
+ // Styling
472
+ width = input('160px', ...(ngDevMode ? [{ debugName: "width" }] : []));
473
+ minWidth = input('50px', ...(ngDevMode ? [{ debugName: "minWidth" }] : []));
474
+ maxWidth = input('', ...(ngDevMode ? [{ debugName: "maxWidth" }] : []));
475
+ textAlign = input('left', ...(ngDevMode ? [{ debugName: "textAlign" }] : []));
476
+ className = input('', ...(ngDevMode ? [{ debugName: "className" }] : []));
477
+ hideOverflow = input(true, ...(ngDevMode ? [{ debugName: "hideOverflow" }] : []));
478
+ // Filtering
479
+ filterType = input('text', ...(ngDevMode ? [{ debugName: "filterType" }] : []));
480
+ filterData = input([], ...(ngDevMode ? [{ debugName: "filterData" }] : []));
481
+ filterValue = input(...(ngDevMode ? [undefined, { debugName: "filterValue" }] : []));
482
+ filterOperator = signal('contains', ...(ngDevMode ? [{ debugName: "filterOperator" }] : []));
483
+ // Formatting
484
+ format = input(null, ...(ngDevMode ? [{ debugName: "format" }] : []));
485
+ symbol = input('$', ...(ngDevMode ? [{ debugName: "symbol" }] : []));
486
+ fraction = input(2, ...(ngDevMode ? [{ debugName: "fraction" }] : []));
487
+ showSymbolInFront = input(true, ...(ngDevMode ? [{ debugName: "showSymbolInFront" }] : []));
488
+ // Boolean Display
489
+ showCheckbox = input(false, ...(ngDevMode ? [{ debugName: "showCheckbox" }] : []));
490
+ booleanData = input(['Yes', 'No'], ...(ngDevMode ? [{ debugName: "booleanData" }] : []));
491
+ // Signals for dynamic values
492
+ visibleSignal = linkedSignal(() => this.visible(), ...(ngDevMode ? [{ debugName: "visibleSignal" }] : []));
493
+ widthSignal = linkedSignal(() => this.width(), ...(ngDevMode ? [{ debugName: "widthSignal" }] : []));
494
+ filterValueSignal = linkedSignal(() => this.filterValue(), ...(ngDevMode ? [{ debugName: "filterValueSignal" }] : []));
495
+ // Template References (will be set by directives)
496
+ cellTemplate;
497
+ headerTemplate;
498
+ footerTemplate;
499
+ // Unique identifier
500
+ timestamp = signal(`${new Date().getTime()}-${Math.random().toString(36).slice(2, 11)}`, ...(ngDevMode ? [{ debugName: "timestamp" }] : []));
501
+ getDisplayTitle() {
502
+ return this.header() || this.title() || this.capitalizeFirstLetter(this.field());
503
+ }
504
+ capitalizeFirstLetter(str) {
505
+ if (!str)
506
+ return '';
507
+ return str
508
+ .replace(/([A-Z])/g, ' $1')
509
+ .replace(/_/g, ' ')
510
+ .split(' ')
511
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
512
+ .join(' ')
513
+ .trim();
514
+ }
515
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
516
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.2", type: GridColumnComponent, isStandalone: true, selector: "nlabs-grid-column", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, frozen: { classPropertyName: "frozen", publicName: "frozen", isSignal: true, isRequired: false, transformFunction: null }, locked: { classPropertyName: "locked", publicName: "locked", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, textAlign: { classPropertyName: "textAlign", publicName: "textAlign", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, hideOverflow: { classPropertyName: "hideOverflow", publicName: "hideOverflow", isSignal: true, isRequired: false, transformFunction: null }, filterType: { classPropertyName: "filterType", publicName: "filterType", isSignal: true, isRequired: false, transformFunction: null }, filterData: { classPropertyName: "filterData", publicName: "filterData", isSignal: true, isRequired: false, transformFunction: null }, filterValue: { classPropertyName: "filterValue", publicName: "filterValue", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, symbol: { classPropertyName: "symbol", publicName: "symbol", isSignal: true, isRequired: false, transformFunction: null }, fraction: { classPropertyName: "fraction", publicName: "fraction", isSignal: true, isRequired: false, transformFunction: null }, showSymbolInFront: { classPropertyName: "showSymbolInFront", publicName: "showSymbolInFront", isSignal: true, isRequired: false, transformFunction: null }, showCheckbox: { classPropertyName: "showCheckbox", publicName: "showCheckbox", isSignal: true, isRequired: false, transformFunction: null }, booleanData: { classPropertyName: "booleanData", publicName: "booleanData", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true });
517
+ }
518
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridColumnComponent, decorators: [{
519
+ type: Component,
520
+ args: [{
521
+ selector: 'nlabs-grid-column',
522
+ standalone: true,
523
+ template: '',
524
+ }]
525
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], frozen: [{ type: i0.Input, args: [{ isSignal: true, alias: "frozen", required: false }] }], locked: [{ type: i0.Input, args: [{ isSignal: true, alias: "locked", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], textAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "textAlign", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], hideOverflow: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideOverflow", required: false }] }], filterType: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterType", required: false }] }], filterData: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterData", required: false }] }], filterValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterValue", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], symbol: [{ type: i0.Input, args: [{ isSignal: true, alias: "symbol", required: false }] }], fraction: [{ type: i0.Input, args: [{ isSignal: true, alias: "fraction", required: false }] }], showSymbolInFront: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSymbolInFront", required: false }] }], showCheckbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCheckbox", required: false }] }], booleanData: [{ type: i0.Input, args: [{ isSignal: true, alias: "booleanData", required: false }] }] } });
526
+
527
+ class DataGridComponent {
528
+ gridDataService;
529
+ themeService;
530
+ config;
531
+ adapter;
532
+ autoLoad = true;
533
+ lazy = true;
534
+ data;
535
+ totalRecords;
536
+ gridHeader;
537
+ gridBody;
538
+ // Theme inputs
539
+ theme = input('auto', ...(ngDevMode ? [{ debugName: "theme" }] : []));
540
+ showThemeSelector = input(true, ...(ngDevMode ? [{ debugName: "showThemeSelector" }] : []));
541
+ // Feature flags
542
+ showColumnChooser = input(true, ...(ngDevMode ? [{ debugName: "showColumnChooser" }] : []));
543
+ showGlobalSearch = input(true, ...(ngDevMode ? [{ debugName: "showGlobalSearch" }] : []));
544
+ showAddButton = input(false, ...(ngDevMode ? [{ debugName: "showAddButton" }] : []));
545
+ addButtonText = input('Add New', ...(ngDevMode ? [{ debugName: "addButtonText" }] : []));
546
+ addButtonUrl = input(undefined, ...(ngDevMode ? [{ debugName: "addButtonUrl" }] : []));
547
+ showExport = input(true, ...(ngDevMode ? [{ debugName: "showExport" }] : []));
548
+ exportFileName = input('export', ...(ngDevMode ? [{ debugName: "exportFileName" }] : []));
549
+ showFooter = input(true, ...(ngDevMode ? [{ debugName: "showFooter" }] : []));
550
+ // Events
551
+ addClick = new EventEmitter();
552
+ excelExport = new EventEmitter();
553
+ pdfExport = new EventEmitter();
554
+ onRefresh = new EventEmitter();
555
+ // Template references from content projection
556
+ columnComponents;
557
+ columnCommandTemplates;
558
+ // Command templates
559
+ captionCommandTemplate = contentChild(GridCaptionCommandTemplateDirective, { ...(ngDevMode ? { debugName: "captionCommandTemplate" } : {}), read: TemplateRef });
560
+ footerTemplate = contentChild(GridFooterTemplateDirective, { ...(ngDevMode ? { debugName: "footerTemplate" } : {}), read: TemplateRef });
561
+ // Actions template
562
+ actionsTemplate;
563
+ checkboxTemplate;
564
+ dataLoad = new EventEmitter();
565
+ rowSelect = new EventEmitter();
566
+ rowUnselect = new EventEmitter();
567
+ stateChange = new EventEmitter();
568
+ gridData = [];
569
+ loading = false;
570
+ Math = Math;
571
+ globalSearchTerm = '';
572
+ state = {
573
+ page: 0,
574
+ pageSize: 10,
575
+ filters: new Map()
576
+ };
577
+ pagination = {
578
+ page: 0,
579
+ pageSize: 10,
580
+ totalRecords: 0,
581
+ totalPages: 0
582
+ };
583
+ selectedRows = new Set();
584
+ sortMetadata = [];
585
+ filterMetadata = [];
586
+ // Column management
587
+ displayedColumns = [];
588
+ draggedColumn;
589
+ dropTargetIndex;
590
+ resizingColumn;
591
+ resizeStartX = 0;
592
+ resizeStartWidth = 0;
593
+ showColumnChooserModal = false;
594
+ // Filter Modal
595
+ showFilterModal = false;
596
+ currentFilterColumn = null;
597
+ currentFilterOperator = 'contains';
598
+ filterValue = '';
599
+ constructor(gridDataService, themeService) {
600
+ this.gridDataService = gridDataService;
601
+ this.themeService = themeService;
602
+ }
603
+ ngAfterContentInit() {
604
+ // Process column components from content projection
605
+ this.processColumnComponents();
606
+ // Process templates
607
+ this.processTemplates();
608
+ // Watch for column changes
609
+ this.columnComponents.changes.subscribe(() => {
610
+ this.processColumnComponents();
611
+ });
612
+ // Watch for template changes
613
+ this.columnCommandTemplates.changes.subscribe(() => {
614
+ this.processTemplates();
615
+ });
616
+ }
617
+ processTemplates() {
618
+ if (this.columnCommandTemplates && this.columnCommandTemplates.length > 0) {
619
+ this.columnCommandTemplates.forEach(directive => {
620
+ const name = directive.name;
621
+ const templateRef = directive.templateRef;
622
+ // Store the template based on name
623
+ if (name === 'actions') {
624
+ this.actionsTemplate = templateRef;
625
+ }
626
+ else if (name === 'checkbox' || name === 'select') {
627
+ this.checkboxTemplate = templateRef;
628
+ }
629
+ });
630
+ }
631
+ }
632
+ ngOnInit() {
633
+ this.initializeGrid();
634
+ if (this.autoLoad && this.lazy && this.adapter) {
635
+ this.loadData();
636
+ }
637
+ else if (!this.lazy && this.data) {
638
+ this.gridData = this.data;
639
+ this.pagination.totalRecords = this.totalRecords || this.data.length;
640
+ this.updatePagination();
641
+ }
642
+ }
643
+ ngOnChanges(changes) {
644
+ if (changes['data'] && !changes['data'].firstChange && !this.lazy) {
645
+ this.gridData = this.data || [];
646
+ this.pagination.totalRecords = this.totalRecords || this.gridData.length;
647
+ this.updatePagination();
648
+ }
649
+ // Adapter değiştiğinde veriyi yeniden yükle
650
+ if (changes['adapter'] && !changes['adapter'].firstChange && this.lazy) {
651
+ this.gridDataService.setAdapter(this.adapter);
652
+ this.state.page = 0;
653
+ this.pagination.page = 0;
654
+ this.loadData();
655
+ }
656
+ }
657
+ ngAfterViewInit() {
658
+ // Sync horizontal scroll between header and body
659
+ if (this.gridBody?.nativeElement && this.gridHeader?.nativeElement) {
660
+ const bodyEl = this.gridBody.nativeElement;
661
+ const headerEl = this.gridHeader.nativeElement;
662
+ bodyEl.addEventListener('scroll', () => {
663
+ headerEl.scrollLeft = bodyEl.scrollLeft;
664
+ });
665
+ }
666
+ }
667
+ initializeGrid() {
668
+ // Initialize pagination from config or use defaults
669
+ const configPageSize = this.config?.pageSize || 10;
670
+ this.state.pageSize = configPageSize;
671
+ this.pagination.pageSize = this.state.pageSize;
672
+ // Initialize displayed columns (will be overridden if using content projection)
673
+ if (this.config?.columns && this.config.columns.length > 0) {
674
+ this.displayedColumns = this.config.columns.map(col => ({
675
+ ...col,
676
+ visible: col.visible !== false,
677
+ resizable: col.resizable !== false
678
+ }));
679
+ }
680
+ if (this.adapter) {
681
+ this.gridDataService.setAdapter(this.adapter);
682
+ }
683
+ // Apply theme from input
684
+ if (this.theme()) {
685
+ this.themeService.setTheme(this.theme());
686
+ }
687
+ }
688
+ processColumnComponents() {
689
+ if (!this.columnComponents || this.columnComponents.length === 0) {
690
+ return;
691
+ }
692
+ // Convert column components to GridColumn format
693
+ this.displayedColumns = this.columnComponents.map(comp => {
694
+ // Get cell template from directive
695
+ const cellDirective = comp.cellTemplate;
696
+ const headerDirective = comp.headerTemplate;
697
+ const footerDirective = comp.footerTemplate;
698
+ const column = {
699
+ field: comp.field(),
700
+ header: comp.getDisplayTitle(),
701
+ sortable: comp.sortable(),
702
+ filterable: comp.filterable(),
703
+ visible: comp.visible(),
704
+ resizable: comp.resizable(),
705
+ width: comp.width(),
706
+ minWidth: comp.minWidth(),
707
+ maxWidth: comp.maxWidth(),
708
+ type: comp.filterType(),
709
+ cellTemplate: cellDirective,
710
+ frozen: comp.frozen(),
711
+ format: comp.format()
712
+ };
713
+ return column;
714
+ });
715
+ }
716
+ loadData() {
717
+ if (!this.lazy) {
718
+ return;
719
+ }
720
+ this.loading = true;
721
+ const request = {
722
+ skip: this.state.page * this.state.pageSize,
723
+ top: this.state.pageSize,
724
+ orderBy: this.buildOrderBy(),
725
+ filter: this.buildFilter(),
726
+ globalSearch: this.globalSearchTerm?.trim() || undefined
727
+ };
728
+ this.gridDataService.getData(request).subscribe({
729
+ next: (result) => {
730
+ this.gridData = result.data;
731
+ this.pagination.totalRecords = result.total;
732
+ this.updatePagination();
733
+ this.loading = false;
734
+ this.dataLoad.emit(result);
735
+ },
736
+ error: (error) => {
737
+ console.error('Error loading grid data:', error);
738
+ this.loading = false;
739
+ }
740
+ });
741
+ }
742
+ onPageChange(page) {
743
+ this.state.page = page;
744
+ this.pagination.page = page;
745
+ this.emitStateChange();
746
+ if (this.lazy) {
747
+ this.loadData();
748
+ }
749
+ }
750
+ onPageSizeChange(pageSize) {
751
+ this.state.pageSize = pageSize;
752
+ this.pagination.pageSize = pageSize;
753
+ this.state.page = 0;
754
+ this.pagination.page = 0;
755
+ this.emitStateChange();
756
+ if (this.lazy) {
757
+ this.loadData();
758
+ }
759
+ }
760
+ onSort(column) {
761
+ if (!column.sortable) {
762
+ return;
763
+ }
764
+ const existingSort = this.sortMetadata.find(s => s.field === column.field);
765
+ if (existingSort) {
766
+ existingSort.order = existingSort.order === 'asc' ? 'desc' : 'asc';
767
+ }
768
+ else {
769
+ this.sortMetadata = [{ field: column.field, order: 'asc' }];
770
+ }
771
+ this.state.sortField = column.field;
772
+ this.state.sortOrder = this.sortMetadata[0].order;
773
+ this.emitStateChange();
774
+ if (this.lazy) {
775
+ this.loadData();
776
+ }
777
+ else {
778
+ this.sortLocal();
779
+ }
780
+ }
781
+ onFilter(column, value) {
782
+ if (!column.filterable) {
783
+ return;
784
+ }
785
+ if (value && value.trim()) {
786
+ this.filterMetadata = this.filterMetadata.filter(f => f.field !== column.field);
787
+ this.filterMetadata.push({
788
+ field: column.field,
789
+ operator: 'and',
790
+ value: value,
791
+ matchMode: 'contains'
792
+ });
793
+ this.state.filters?.set(column.field, value);
794
+ }
795
+ else {
796
+ this.filterMetadata = this.filterMetadata.filter(f => f.field !== column.field);
797
+ this.state.filters?.delete(column.field);
798
+ }
799
+ this.state.page = 0;
800
+ this.pagination.page = 0;
801
+ this.emitStateChange();
802
+ if (this.lazy) {
803
+ this.loadData();
804
+ }
805
+ else {
806
+ this.filterLocal();
807
+ }
808
+ }
809
+ onRowSelect(row, event) {
810
+ if (!this.config?.selectable) {
811
+ return;
812
+ }
813
+ // Stop event propagation if event exists
814
+ if (event) {
815
+ event.stopPropagation();
816
+ }
817
+ if (this.config?.multiSelect) {
818
+ if (this.selectedRows.has(row)) {
819
+ this.selectedRows.delete(row);
820
+ this.rowUnselect.emit(row);
821
+ }
822
+ else {
823
+ this.selectedRows.add(row);
824
+ this.rowSelect.emit(row);
825
+ }
826
+ }
827
+ else {
828
+ this.selectedRows.clear();
829
+ this.selectedRows.add(row);
830
+ this.rowSelect.emit(row);
831
+ }
832
+ this.state.selectedRows = Array.from(this.selectedRows);
833
+ }
834
+ toggleSelectAll(event) {
835
+ const checkbox = event.target;
836
+ if (checkbox.checked) {
837
+ // Select all rows on current page
838
+ this.gridData.forEach(row => this.selectedRows.add(row));
839
+ }
840
+ else {
841
+ // Deselect all rows
842
+ this.selectedRows.clear();
843
+ }
844
+ this.state.selectedRows = Array.from(this.selectedRows);
845
+ }
846
+ isAllSelected() {
847
+ if (this.gridData.length === 0)
848
+ return false;
849
+ return this.gridData.every(row => this.selectedRows.has(row));
850
+ }
851
+ isSomeSelected() {
852
+ if (this.gridData.length === 0)
853
+ return false;
854
+ const selectedCount = this.gridData.filter(row => this.selectedRows.has(row)).length;
855
+ return selectedCount > 0 && selectedCount < this.gridData.length;
856
+ }
857
+ isRowSelected(row) {
858
+ return this.selectedRows.has(row);
859
+ }
860
+ buildOrderBy() {
861
+ if (this.sortMetadata.length === 0) {
862
+ return undefined;
863
+ }
864
+ return this.gridDataService.buildSortString(this.sortMetadata);
865
+ }
866
+ buildFilter() {
867
+ if (this.filterMetadata.length === 0) {
868
+ return undefined;
869
+ }
870
+ return this.gridDataService.buildFilterString(this.filterMetadata);
871
+ }
872
+ sortLocal() {
873
+ if (this.sortMetadata.length === 0) {
874
+ return;
875
+ }
876
+ const sort = this.sortMetadata[0];
877
+ this.gridData = [...this.gridData].sort((a, b) => {
878
+ const aVal = a[sort.field];
879
+ const bVal = b[sort.field];
880
+ if (aVal === bVal)
881
+ return 0;
882
+ const result = aVal < bVal ? -1 : 1;
883
+ return sort.order === 'asc' ? result : -result;
884
+ });
885
+ }
886
+ filterLocal() {
887
+ if (!this.data) {
888
+ return;
889
+ }
890
+ let filtered = [...this.data];
891
+ this.filterMetadata.forEach(filter => {
892
+ filtered = filtered.filter((item) => {
893
+ const value = item[filter.field];
894
+ const filterValue = filter.value?.toLowerCase() || '';
895
+ // Handle isEmpty and isNotEmpty
896
+ if (filter.matchMode === 'isEmpty') {
897
+ return value === null || value === undefined || value === '';
898
+ }
899
+ if (filter.matchMode === 'isNotEmpty') {
900
+ return value !== null && value !== undefined && value !== '';
901
+ }
902
+ if (value === null || value === undefined) {
903
+ return false;
904
+ }
905
+ const strValue = String(value).toLowerCase();
906
+ const numValue = Number(value);
907
+ const numFilterValue = Number(filter.value);
908
+ // Date comparison
909
+ const dateValue = value instanceof Date ? value : new Date(value);
910
+ const dateFilterValue = new Date(filter.value);
911
+ const isValidDate = dateValue instanceof Date && !isNaN(dateValue.getTime());
912
+ const isValidFilterDate = dateFilterValue instanceof Date && !isNaN(dateFilterValue.getTime());
913
+ switch (filter.matchMode) {
914
+ case 'contains':
915
+ return strValue.includes(filterValue);
916
+ case 'notContains':
917
+ return !strValue.includes(filterValue);
918
+ case 'startsWith':
919
+ return strValue.startsWith(filterValue);
920
+ case 'endsWith':
921
+ return strValue.endsWith(filterValue);
922
+ case 'equals':
923
+ // For dates, compare date parts only
924
+ if (isValidDate && isValidFilterDate) {
925
+ return dateValue.toDateString() === dateFilterValue.toDateString();
926
+ }
927
+ return strValue === filterValue || value === filter.value;
928
+ case 'notEquals':
929
+ // For dates, compare date parts only
930
+ if (isValidDate && isValidFilterDate) {
931
+ return dateValue.toDateString() !== dateFilterValue.toDateString();
932
+ }
933
+ return strValue !== filterValue && value !== filter.value;
934
+ case 'lt':
935
+ if (isValidDate && isValidFilterDate) {
936
+ return dateValue.getTime() < dateFilterValue.getTime();
937
+ }
938
+ return !isNaN(numValue) && !isNaN(numFilterValue) && numValue < numFilterValue;
939
+ case 'lte':
940
+ if (isValidDate && isValidFilterDate) {
941
+ return dateValue.getTime() <= dateFilterValue.getTime();
942
+ }
943
+ return !isNaN(numValue) && !isNaN(numFilterValue) && numValue <= numFilterValue;
944
+ case 'gt':
945
+ if (isValidDate && isValidFilterDate) {
946
+ return dateValue.getTime() > dateFilterValue.getTime();
947
+ }
948
+ return !isNaN(numValue) && !isNaN(numFilterValue) && numValue > numFilterValue;
949
+ case 'gte':
950
+ if (isValidDate && isValidFilterDate) {
951
+ return dateValue.getTime() >= dateFilterValue.getTime();
952
+ }
953
+ return !isNaN(numValue) && !isNaN(numFilterValue) && numValue >= numFilterValue;
954
+ default:
955
+ return strValue.includes(filterValue);
956
+ }
957
+ });
958
+ });
959
+ this.gridData = filtered;
960
+ this.pagination.totalRecords = filtered.length;
961
+ this.updatePagination();
962
+ }
963
+ updatePagination() {
964
+ this.pagination.totalPages = Math.ceil(this.pagination.totalRecords / this.pagination.pageSize);
965
+ }
966
+ emitStateChange() {
967
+ this.stateChange.emit({ ...this.state });
968
+ }
969
+ trackByRow(index, row) {
970
+ // Try to use id, _id, or unique identifier, fallback to index + stringified row
971
+ return row?.id ?? row?._id ?? row?.Id ?? `${index}-${JSON.stringify(row)}`;
972
+ }
973
+ getCellValue(row, column) {
974
+ const value = row[column.field];
975
+ // Format is now just a hint string (like 'c', 'd', 'p'), not a function
976
+ // Actual formatting should be done via pipes in template
977
+ return value;
978
+ }
979
+ getSortIcon(column) {
980
+ if (!column.sortable) {
981
+ return '';
982
+ }
983
+ const sort = this.sortMetadata.find(s => s.field === column.field);
984
+ if (!sort) {
985
+ return '⇅';
986
+ }
987
+ return sort.order === 'asc' ? '↑' : '↓';
988
+ }
989
+ getFilterValue(column) {
990
+ const filter = this.filterMetadata.find(f => f.field === column.field);
991
+ return filter ? filter.value : '';
992
+ }
993
+ // Filter Modal Methods
994
+ openFilterModal(column, event) {
995
+ event.stopPropagation();
996
+ if (column.filterable === false) {
997
+ return;
998
+ }
999
+ this.currentFilterColumn = column;
1000
+ // Load existing filter
1001
+ const existingFilter = this.filterMetadata.find(f => f.field === column.field);
1002
+ if (existingFilter) {
1003
+ this.filterValue = existingFilter.value;
1004
+ this.currentFilterOperator = existingFilter.matchMode;
1005
+ }
1006
+ else {
1007
+ this.filterValue = '';
1008
+ // Set default operator based on filterType
1009
+ const filterType = column.filterType || 'text';
1010
+ if (filterType === 'number' || filterType === 'date') {
1011
+ this.currentFilterOperator = 'equals';
1012
+ }
1013
+ else {
1014
+ this.currentFilterOperator = 'contains';
1015
+ }
1016
+ }
1017
+ this.showFilterModal = true;
1018
+ }
1019
+ closeFilterModal() {
1020
+ this.showFilterModal = false;
1021
+ this.currentFilterColumn = null;
1022
+ this.filterValue = '';
1023
+ }
1024
+ applyFilterModal() {
1025
+ if (!this.currentFilterColumn)
1026
+ return;
1027
+ const field = this.currentFilterColumn.field;
1028
+ // Remove existing filter for this field
1029
+ this.filterMetadata = this.filterMetadata.filter(f => f.field !== field);
1030
+ // For isEmpty and isNotEmpty, no value is needed
1031
+ const needsValue = this.currentFilterOperator !== 'isEmpty' && this.currentFilterOperator !== 'isNotEmpty';
1032
+ // Add new filter if value is not empty OR if operator doesn't need value
1033
+ if (!needsValue || (this.filterValue && this.filterValue.trim())) {
1034
+ this.filterMetadata.push({
1035
+ field: field,
1036
+ operator: 'and',
1037
+ value: needsValue ? this.filterValue.trim() : '',
1038
+ matchMode: this.currentFilterOperator
1039
+ });
1040
+ this.state.filters?.set(field, needsValue ? this.filterValue.trim() : this.currentFilterOperator);
1041
+ }
1042
+ else {
1043
+ this.state.filters?.delete(field);
1044
+ }
1045
+ this.state.page = 0;
1046
+ this.pagination.page = 0;
1047
+ this.emitStateChange();
1048
+ if (this.lazy) {
1049
+ this.loadData();
1050
+ }
1051
+ else {
1052
+ this.filterLocal();
1053
+ }
1054
+ this.closeFilterModal();
1055
+ }
1056
+ clearFilterModal() {
1057
+ if (!this.currentFilterColumn)
1058
+ return;
1059
+ const field = this.currentFilterColumn.field;
1060
+ this.filterMetadata = this.filterMetadata.filter(f => f.field !== field);
1061
+ this.state.filters?.delete(field);
1062
+ this.state.page = 0;
1063
+ this.pagination.page = 0;
1064
+ this.emitStateChange();
1065
+ if (this.lazy) {
1066
+ this.loadData();
1067
+ }
1068
+ else {
1069
+ this.filterLocal();
1070
+ }
1071
+ this.closeFilterModal();
1072
+ }
1073
+ isFilterActive(column) {
1074
+ return this.filterMetadata.some(f => f.field === column.field);
1075
+ }
1076
+ getPageNumbers() {
1077
+ const pages = [];
1078
+ const total = this.pagination.totalPages;
1079
+ const current = this.pagination.page;
1080
+ if (total <= 7) {
1081
+ for (let i = 0; i < total; i++) {
1082
+ pages.push(i);
1083
+ }
1084
+ }
1085
+ else {
1086
+ if (current <= 3) {
1087
+ for (let i = 0; i < 5; i++)
1088
+ pages.push(i);
1089
+ pages.push(-1);
1090
+ pages.push(total - 1);
1091
+ }
1092
+ else if (current >= total - 4) {
1093
+ pages.push(0);
1094
+ pages.push(-1);
1095
+ for (let i = total - 5; i < total; i++)
1096
+ pages.push(i);
1097
+ }
1098
+ else {
1099
+ pages.push(0);
1100
+ pages.push(-1);
1101
+ for (let i = current - 1; i <= current + 1; i++)
1102
+ pages.push(i);
1103
+ pages.push(-1);
1104
+ pages.push(total - 1);
1105
+ }
1106
+ }
1107
+ return pages;
1108
+ }
1109
+ // Column Management Methods
1110
+ toggleColumnVisibility(column) {
1111
+ column.visible = !column.visible;
1112
+ this.displayedColumns = [...this.displayedColumns];
1113
+ }
1114
+ getVisibleColumns() {
1115
+ return this.displayedColumns.filter(col => col.visible !== false);
1116
+ }
1117
+ // Drag & Drop Methods
1118
+ onDragStart(event, column, index) {
1119
+ if (!this.config?.reorderable)
1120
+ return;
1121
+ this.draggedColumn = column;
1122
+ event.dataTransfer.effectAllowed = 'move';
1123
+ event.dataTransfer.setData('text/html', event.target.innerHTML);
1124
+ }
1125
+ onColumnDragOver(event, index) {
1126
+ if (!this.config?.reorderable || !this.draggedColumn)
1127
+ return;
1128
+ event.preventDefault();
1129
+ event.dataTransfer.dropEffect = 'move';
1130
+ this.dropTargetIndex = index;
1131
+ }
1132
+ onColumnDrop(event, targetIndex) {
1133
+ if (!this.config?.reorderable || !this.draggedColumn)
1134
+ return;
1135
+ event.preventDefault();
1136
+ const visibleColumns = this.getVisibleColumns();
1137
+ const draggedIndex = visibleColumns.indexOf(this.draggedColumn);
1138
+ if (draggedIndex !== targetIndex) {
1139
+ const allColumns = [...this.displayedColumns];
1140
+ const draggedCol = allColumns.splice(draggedIndex, 1)[0];
1141
+ allColumns.splice(targetIndex, 0, draggedCol);
1142
+ this.displayedColumns = allColumns;
1143
+ }
1144
+ this.draggedColumn = undefined;
1145
+ this.dropTargetIndex = undefined;
1146
+ }
1147
+ onColumnDragEnd() {
1148
+ this.draggedColumn = undefined;
1149
+ this.dropTargetIndex = undefined;
1150
+ }
1151
+ // Resize Methods
1152
+ onResizeStart(event, column) {
1153
+ if (!column.resizable)
1154
+ return;
1155
+ event.preventDefault();
1156
+ event.stopPropagation();
1157
+ this.resizingColumn = column;
1158
+ this.resizeStartX = event.pageX;
1159
+ // Get the header cell element
1160
+ const resizeHandle = event.target;
1161
+ const headerCell = resizeHandle.closest('.header-cell');
1162
+ if (headerCell) {
1163
+ headerCell.classList.add('resizing');
1164
+ this.resizeStartWidth = headerCell.offsetWidth;
1165
+ }
1166
+ else {
1167
+ this.resizeStartWidth = 100;
1168
+ }
1169
+ document.addEventListener('mousemove', this.onResize);
1170
+ document.addEventListener('mouseup', this.onResizeEnd);
1171
+ }
1172
+ onResize = (event) => {
1173
+ if (!this.resizingColumn)
1174
+ return;
1175
+ event.preventDefault();
1176
+ const diff = event.pageX - this.resizeStartX;
1177
+ const minWidth = parseInt(this.resizingColumn.minWidth || '50', 10);
1178
+ const maxWidth = this.resizingColumn.maxWidth ? parseInt(this.resizingColumn.maxWidth, 10) : Infinity;
1179
+ let newWidth = Math.max(minWidth, this.resizeStartWidth + diff);
1180
+ if (maxWidth !== Infinity) {
1181
+ newWidth = Math.min(newWidth, maxWidth);
1182
+ }
1183
+ // Update column width directly
1184
+ this.resizingColumn.width = `${newWidth}px`;
1185
+ };
1186
+ onResizeEnd = () => {
1187
+ // Remove resizing class
1188
+ const resizingElement = document.querySelector('.header-cell.resizing');
1189
+ if (resizingElement) {
1190
+ resizingElement.classList.remove('resizing');
1191
+ }
1192
+ this.resizingColumn = undefined;
1193
+ document.removeEventListener('mousemove', this.onResize);
1194
+ document.removeEventListener('mouseup', this.onResizeEnd);
1195
+ };
1196
+ // Global Search
1197
+ onGlobalSearch(term) {
1198
+ this.globalSearchTerm = term;
1199
+ if (!term.trim()) {
1200
+ this.loadData();
1201
+ return;
1202
+ }
1203
+ // Filter data locally or trigger server-side search
1204
+ if (this.lazy && this.adapter) {
1205
+ // Server-side search - reload data with global search term
1206
+ this.state.page = 0;
1207
+ this.loadData();
1208
+ }
1209
+ else {
1210
+ // Client-side search
1211
+ const filteredData = this.gridData.filter((row) => {
1212
+ return this.getVisibleColumns().some(col => {
1213
+ const value = row[col.field];
1214
+ return value?.toString().toLowerCase().includes(term.toLowerCase());
1215
+ });
1216
+ });
1217
+ this.gridData = filteredData;
1218
+ }
1219
+ }
1220
+ // Add Button Handler
1221
+ onAddButtonClick() {
1222
+ if (this.addButtonUrl()) {
1223
+ window.location.href = this.addButtonUrl();
1224
+ }
1225
+ else {
1226
+ this.addClick.emit();
1227
+ }
1228
+ }
1229
+ onRefreshClick() {
1230
+ this.onRefresh.emit();
1231
+ }
1232
+ // Export to Excel
1233
+ exportToExcel() {
1234
+ const dataToExport = this.gridData;
1235
+ this.excelExport.emit(dataToExport);
1236
+ // Generate professional Excel file
1237
+ this.generateExcelFile(dataToExport);
1238
+ }
1239
+ generateExcelFile(data) {
1240
+ if (data.length === 0) {
1241
+ console.warn('No data to export');
1242
+ return;
1243
+ }
1244
+ const columns = this.getVisibleColumns();
1245
+ const today = new Date();
1246
+ const dateStr = today.toLocaleDateString('en-GB', {
1247
+ day: '2-digit',
1248
+ month: '2-digit',
1249
+ year: 'numeric'
1250
+ });
1251
+ const timeStr = today.toLocaleTimeString('en-GB', {
1252
+ hour: '2-digit',
1253
+ minute: '2-digit'
1254
+ });
1255
+ // Create Excel XML with enhanced styling
1256
+ let excelContent = `<?xml version="1.0"?>
1257
+ <?mso-application progid="Excel.Sheet"?>
1258
+ <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
1259
+ xmlns:o="urn:schemas-microsoft-com:office:office"
1260
+ xmlns:x="urn:schemas-microsoft-com:office:excel"
1261
+ xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
1262
+ xmlns:html="http://www.w3.org/TR/REC-html40">
1263
+ <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office">
1264
+ <Title>${this.exportFileName()}</Title>
1265
+ <Author>Nlabs Data Grid</Author>
1266
+ <Created>${today.toISOString()}</Created>
1267
+ <Company>Your Company</Company>
1268
+ </DocumentProperties>
1269
+ <Styles>
1270
+ <Style ss:ID="Title">
1271
+ <Font ss:Bold="1" ss:Size="16" ss:Color="#1F2937"/>
1272
+ <Alignment ss:Horizontal="Left" ss:Vertical="Center"/>
1273
+ </Style>
1274
+ <Style ss:ID="Subtitle">
1275
+ <Font ss:Size="11" ss:Color="#6B7280"/>
1276
+ <Alignment ss:Horizontal="Left" ss:Vertical="Center"/>
1277
+ </Style>
1278
+ <Style ss:ID="Header">
1279
+ <Font ss:Bold="1" ss:Size="11" ss:Color="#FFFFFF"/>
1280
+ <Interior ss:Color="#4F46E5" ss:Pattern="Solid"/>
1281
+ <Alignment ss:Horizontal="Center" ss:Vertical="Center"/>
1282
+ <Borders>
1283
+ <Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="2" ss:Color="#4338CA"/>
1284
+ <Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#4338CA"/>
1285
+ <Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#4338CA"/>
1286
+ <Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#4338CA"/>
1287
+ </Borders>
1288
+ </Style>
1289
+ <Style ss:ID="DataEven">
1290
+ <Interior ss:Color="#F9FAFB" ss:Pattern="Solid"/>
1291
+ <Borders>
1292
+ <Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#E5E7EB"/>
1293
+ <Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#E5E7EB"/>
1294
+ <Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#E5E7EB"/>
1295
+ <Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#E5E7EB"/>
1296
+ </Borders>
1297
+ <Alignment ss:Vertical="Center"/>
1298
+ </Style>
1299
+ <Style ss:ID="DataOdd">
1300
+ <Interior ss:Color="#FFFFFF" ss:Pattern="Solid"/>
1301
+ <Borders>
1302
+ <Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#E5E7EB"/>
1303
+ <Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#E5E7EB"/>
1304
+ <Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#E5E7EB"/>
1305
+ <Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#E5E7EB"/>
1306
+ </Borders>
1307
+ <Alignment ss:Vertical="Center"/>
1308
+ </Style>
1309
+ <Style ss:ID="Footer">
1310
+ <Font ss:Bold="1" ss:Size="10" ss:Color="#6B7280"/>
1311
+ <Interior ss:Color="#F3F4F6" ss:Pattern="Solid"/>
1312
+ <Alignment ss:Horizontal="Right" ss:Vertical="Center"/>
1313
+ <Borders>
1314
+ <Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="2" ss:Color="#D1D5DB"/>
1315
+ </Borders>
1316
+ </Style>
1317
+ </Styles>
1318
+ <Worksheet ss:Name="Data Export">
1319
+ <Table>
1320
+ <!-- Title Row -->
1321
+ <Row ss:Height="24">
1322
+ <Cell ss:StyleID="Title" ss:MergeAcross="${columns.length - 1}">
1323
+ <Data ss:Type="String">📊 ${this.exportFileName()}</Data>
1324
+ </Cell>
1325
+ </Row>
1326
+ <!-- Date/Time Row -->
1327
+ <Row ss:Height="18">
1328
+ <Cell ss:StyleID="Subtitle" ss:MergeAcross="${columns.length - 1}">
1329
+ <Data ss:Type="String">Generated: ${dateStr} at ${timeStr} | Records: ${data.length}</Data>
1330
+ </Cell>
1331
+ </Row>
1332
+ <!-- Empty Row -->
1333
+ <Row ss:Height="10"/>
1334
+ <!-- Header Row -->
1335
+ <Row ss:Height="20">
1336
+ ${columns.map(col => ` <Cell ss:StyleID="Header"><Data ss:Type="String">${this.escapeXml(col.header)}</Data></Cell>`).join('\n')}
1337
+ </Row>
1338
+ <!-- Data Rows -->
1339
+ ${data.map((row, idx) => {
1340
+ const styleId = idx % 2 === 0 ? 'DataEven' : 'DataOdd';
1341
+ return ` <Row ss:Height="18" ss:StyleID="${styleId}">
1342
+ ${columns.map(col => {
1343
+ const value = row[col.field];
1344
+ const type = typeof value === 'number' ? 'Number' : 'String';
1345
+ const displayValue = this.formatCellValue(value, col);
1346
+ return ` <Cell><Data ss:Type="${type}">${this.escapeXml(displayValue)}</Data></Cell>`;
1347
+ }).join('\n')}
1348
+ </Row>`;
1349
+ }).join('\n')}
1350
+ <!-- Empty Row -->
1351
+ <Row ss:Height="10"/>
1352
+ <!-- Footer Row -->
1353
+ <Row ss:Height="18">
1354
+ <Cell ss:StyleID="Footer" ss:MergeAcross="${columns.length - 1}">
1355
+ <Data ss:Type="String">Total Records: ${data.length} | Exported from NLabs Data Grid</Data>
1356
+ </Cell>
1357
+ </Row>
1358
+ </Table>
1359
+ </Worksheet>
1360
+ </Workbook>`;
1361
+ const blob = new Blob([excelContent], {
1362
+ type: 'application/vnd.ms-excel;charset=utf-8;'
1363
+ });
1364
+ const link = document.createElement('a');
1365
+ link.href = URL.createObjectURL(blob);
1366
+ link.download = `${this.exportFileName()}_${dateStr.replace(/\//g, '-')}.xls`;
1367
+ link.click();
1368
+ URL.revokeObjectURL(link.href);
1369
+ }
1370
+ formatCellValue(value, column) {
1371
+ if (value == null || value === '')
1372
+ return '';
1373
+ // Use the column's format function if available
1374
+ if (column.format && typeof column.format === 'function') {
1375
+ try {
1376
+ return column.format(value);
1377
+ }
1378
+ catch (e) {
1379
+ console.warn('Error formatting value:', e);
1380
+ }
1381
+ }
1382
+ // Date formatting
1383
+ if (column.type === 'date') {
1384
+ if (value instanceof Date) {
1385
+ return value.toLocaleDateString('en-GB');
1386
+ }
1387
+ // Try to parse string date
1388
+ const date = new Date(value);
1389
+ if (!isNaN(date.getTime())) {
1390
+ return date.toLocaleDateString('en-GB');
1391
+ }
1392
+ }
1393
+ // Number formatting
1394
+ if (column.type === 'number' && typeof value === 'number') {
1395
+ return value.toLocaleString('en-US', {
1396
+ minimumFractionDigits: 0,
1397
+ maximumFractionDigits: 2
1398
+ });
1399
+ }
1400
+ // Boolean formatting
1401
+ if (column.type === 'boolean') {
1402
+ return value ? 'Yes' : 'No';
1403
+ }
1404
+ return String(value);
1405
+ }
1406
+ escapeXml(text) {
1407
+ return text
1408
+ .replace(/&/g, '&amp;')
1409
+ .replace(/</g, '&lt;')
1410
+ .replace(/>/g, '&gt;')
1411
+ .replace(/"/g, '&quot;')
1412
+ .replace(/'/g, '&apos;');
1413
+ }
1414
+ // Export to PDF / Print
1415
+ exportToPDF() {
1416
+ this.pdfExport.emit(this.gridData);
1417
+ // Generate professional print layout
1418
+ this.generatePrintablePDF();
1419
+ }
1420
+ generatePrintablePDF() {
1421
+ if (this.gridData.length === 0) {
1422
+ console.warn('No data to export');
1423
+ return;
1424
+ }
1425
+ const columns = this.getVisibleColumns();
1426
+ const today = new Date();
1427
+ const dateStr = today.toLocaleDateString('en-GB', {
1428
+ day: '2-digit',
1429
+ month: '2-digit',
1430
+ year: 'numeric'
1431
+ });
1432
+ const timeStr = today.toLocaleTimeString('en-GB', {
1433
+ hour: '2-digit',
1434
+ minute: '2-digit'
1435
+ });
1436
+ const printWindow = window.open('', '_blank');
1437
+ if (!printWindow) {
1438
+ console.error('Failed to open print window');
1439
+ return;
1440
+ }
1441
+ const htmlContent = `
1442
+ <!DOCTYPE html>
1443
+ <html lang="en">
1444
+ <head>
1445
+ <meta charset="UTF-8">
1446
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1447
+ <title>${this.exportFileName()} - PDF Export</title>
1448
+ <style>
1449
+ * {
1450
+ margin: 0;
1451
+ padding: 0;
1452
+ box-sizing: border-box;
1453
+ }
1454
+
1455
+ body {
1456
+ font-family: 'Segoe UI', Arial, sans-serif;
1457
+ padding: 30px;
1458
+ background: white;
1459
+ color: #1f2937;
1460
+ }
1461
+
1462
+ .header {
1463
+ margin-bottom: 30px;
1464
+ border-bottom: 3px solid #4f46e5;
1465
+ padding-bottom: 20px;
1466
+ }
1467
+
1468
+ .header h1 {
1469
+ color: #4f46e5;
1470
+ font-size: 28px;
1471
+ margin-bottom: 8px;
1472
+ font-weight: 600;
1473
+ display: flex;
1474
+ align-items: center;
1475
+ gap: 10px;
1476
+ }
1477
+
1478
+ .export-info {
1479
+ display: flex;
1480
+ justify-content: space-between;
1481
+ color: #6b7280;
1482
+ font-size: 13px;
1483
+ margin-top: 8px;
1484
+ }
1485
+
1486
+ .export-date {
1487
+ font-weight: 500;
1488
+ }
1489
+
1490
+ .export-stats {
1491
+ font-weight: 500;
1492
+ }
1493
+
1494
+ table {
1495
+ border-collapse: collapse;
1496
+ width: 100%;
1497
+ margin-top: 20px;
1498
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
1499
+ }
1500
+
1501
+ thead {
1502
+ background: linear-gradient(135deg, #4f46e5 0%, #6366f1 100%);
1503
+ }
1504
+
1505
+ th {
1506
+ color: white;
1507
+ padding: 14px 12px;
1508
+ text-align: left;
1509
+ font-weight: 600;
1510
+ font-size: 12px;
1511
+ text-transform: uppercase;
1512
+ letter-spacing: 0.5px;
1513
+ border-right: 1px solid rgba(255,255,255,0.1);
1514
+ }
1515
+
1516
+ th:last-child {
1517
+ border-right: none;
1518
+ }
1519
+
1520
+ td {
1521
+ border: 1px solid #e5e7eb;
1522
+ padding: 12px;
1523
+ font-size: 13px;
1524
+ }
1525
+
1526
+ tbody tr:nth-child(even) {
1527
+ background: #f9fafb;
1528
+ }
1529
+
1530
+ tbody tr:hover {
1531
+ background: #f3f4f6;
1532
+ }
1533
+
1534
+ .footer {
1535
+ margin-top: 30px;
1536
+ padding-top: 20px;
1537
+ border-top: 2px solid #e5e7eb;
1538
+ text-align: center;
1539
+ color: #9ca3af;
1540
+ font-size: 12px;
1541
+ }
1542
+
1543
+ .footer-stats {
1544
+ display: flex;
1545
+ justify-content: center;
1546
+ gap: 30px;
1547
+ margin-top: 10px;
1548
+ }
1549
+
1550
+ .footer-stat {
1551
+ display: flex;
1552
+ align-items: center;
1553
+ gap: 5px;
1554
+ }
1555
+
1556
+ .footer-stat strong {
1557
+ color: #4f46e5;
1558
+ }
1559
+
1560
+ @media print {
1561
+ body {
1562
+ padding: 20px;
1563
+ }
1564
+
1565
+ .header {
1566
+ page-break-after: avoid;
1567
+ }
1568
+
1569
+ table {
1570
+ page-break-inside: auto;
1571
+ }
1572
+
1573
+ tr {
1574
+ page-break-inside: avoid;
1575
+ page-break-after: auto;
1576
+ }
1577
+
1578
+ thead {
1579
+ display: table-header-group;
1580
+ }
1581
+
1582
+ tfoot {
1583
+ display: table-footer-group;
1584
+ }
1585
+
1586
+ tbody tr:hover {
1587
+ background: inherit;
1588
+ }
1589
+ }
1590
+
1591
+ @page {
1592
+ margin: 20mm;
1593
+ size: A4 landscape;
1594
+ }
1595
+ </style>
1596
+ </head>
1597
+ <body>
1598
+ <div class="header">
1599
+ <h1>
1600
+ <span>📊</span>
1601
+ <span>${this.exportFileName()}</span>
1602
+ </h1>
1603
+ <div class="export-info">
1604
+ <div class="export-date">
1605
+ Generated: ${dateStr} at ${timeStr}
1606
+ </div>
1607
+ <div class="export-stats">
1608
+ Total Records: ${this.gridData.length} | Columns: ${columns.length}
1609
+ </div>
1610
+ </div>
1611
+ </div>
1612
+
1613
+ <table>
1614
+ <thead>
1615
+ <tr>
1616
+ ${columns.map(col => `<th>${this.escapeHtml(col.header)}</th>`).join('')}
1617
+ </tr>
1618
+ </thead>
1619
+ <tbody>
1620
+ ${this.gridData.map((row, idx) => `
1621
+ <tr>
1622
+ ${columns.map(col => {
1623
+ const value = row[col.field];
1624
+ const formattedValue = this.formatCellValue(value, col);
1625
+ return `<td>${this.escapeHtml(formattedValue)}</td>`;
1626
+ }).join('')}
1627
+ </tr>
1628
+ `).join('')}
1629
+ </tbody>
1630
+ </table>
1631
+
1632
+ <div class="footer">
1633
+ <div>Exported from NLabs Data Grid | © ${today.getFullYear()}</div>
1634
+ <div class="footer-stats">
1635
+ <div class="footer-stat">
1636
+ <span>📄 Pages:</span>
1637
+ <strong>Auto</strong>
1638
+ </div>
1639
+ <div class="footer-stat">
1640
+ <span>📊 Records:</span>
1641
+ <strong>${this.gridData.length}</strong>
1642
+ </div>
1643
+ <div class="footer-stat">
1644
+ <span>📅 Date:</span>
1645
+ <strong>${dateStr}</strong>
1646
+ </div>
1647
+ </div>
1648
+ </div>
1649
+
1650
+ <script>
1651
+ window.onload = function() {
1652
+ // Auto-print after a short delay
1653
+ setTimeout(function() {
1654
+ window.print();
1655
+ }, 500);
1656
+ };
1657
+ </script>
1658
+ </body>
1659
+ </html>
1660
+ `;
1661
+ printWindow.document.write(htmlContent);
1662
+ printWindow.document.close();
1663
+ }
1664
+ escapeHtml(text) {
1665
+ const div = document.createElement('div');
1666
+ div.textContent = text;
1667
+ return div.innerHTML;
1668
+ }
1669
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: DataGridComponent, deps: [{ token: GridDataService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
1670
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: DataGridComponent, isStandalone: true, selector: "nlabs-data-grid", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: false, isRequired: false, transformFunction: null }, adapter: { classPropertyName: "adapter", publicName: "adapter", isSignal: false, isRequired: false, transformFunction: null }, autoLoad: { classPropertyName: "autoLoad", publicName: "autoLoad", isSignal: false, isRequired: false, transformFunction: null }, lazy: { classPropertyName: "lazy", publicName: "lazy", isSignal: false, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: false, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: false, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, showThemeSelector: { classPropertyName: "showThemeSelector", publicName: "showThemeSelector", isSignal: true, isRequired: false, transformFunction: null }, showColumnChooser: { classPropertyName: "showColumnChooser", publicName: "showColumnChooser", isSignal: true, isRequired: false, transformFunction: null }, showGlobalSearch: { classPropertyName: "showGlobalSearch", publicName: "showGlobalSearch", isSignal: true, isRequired: false, transformFunction: null }, showAddButton: { classPropertyName: "showAddButton", publicName: "showAddButton", isSignal: true, isRequired: false, transformFunction: null }, addButtonText: { classPropertyName: "addButtonText", publicName: "addButtonText", isSignal: true, isRequired: false, transformFunction: null }, addButtonUrl: { classPropertyName: "addButtonUrl", publicName: "addButtonUrl", isSignal: true, isRequired: false, transformFunction: null }, showExport: { classPropertyName: "showExport", publicName: "showExport", isSignal: true, isRequired: false, transformFunction: null }, exportFileName: { classPropertyName: "exportFileName", publicName: "exportFileName", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addClick: "addClick", excelExport: "excelExport", pdfExport: "pdfExport", onRefresh: "onRefresh", dataLoad: "dataLoad", rowSelect: "rowSelect", rowUnselect: "rowUnselect", stateChange: "stateChange" }, providers: [GridDataService, ThemeService], queries: [{ propertyName: "captionCommandTemplate", first: true, predicate: GridCaptionCommandTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "footerTemplate", first: true, predicate: GridFooterTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "columnComponents", predicate: GridColumnComponent }, { propertyName: "columnCommandTemplates", predicate: GridColumnCommandTemplateDirective }], viewQueries: [{ propertyName: "gridHeader", first: true, predicate: ["gridHeader"], descendants: true, read: ElementRef }, { propertyName: "gridBody", first: true, predicate: ["gridBody"], descendants: true, read: ElementRef }], usesOnChanges: true, ngImport: i0, template: "<div class=\"nlabs-data-grid\" [class.loading]=\"loading\">\n <!-- Loading Overlay -->\n @if (loading) {\n <div class=\"grid-loading-overlay\">\n <div class=\"spinner\"></div>\n </div>\n }\n\n\n <!-- Grid Container -->\n <div class=\"grid-container\">\n <!-- Toolbar with Global Search, Export, Theme Selector -->\n @if (config?.showHeader !== false) {\n <div class=\"grid-toolbar\">\n <div class=\"toolbar-left\">\n <!-- Global Search -->\n @if(showGlobalSearch()){\n <div class=\"global-search\">\n <input type=\"text\" class=\"global-search-input\" placeholder=\"\uD83D\uDD0D Search in all columns...\"\n [(ngModel)]=\"globalSearchTerm\" (input)=\"onGlobalSearch($any($event.target).value)\" />\n </div>\n }\n </div>\n <div class=\"toolbar-center\">\n <!-- Add Button -->\n @if (showAddButton()) {\n <button class=\"toolbar-btn btn-add\" (click)=\"onAddButtonClick()\" title=\"Add new record\">\n \u2795 {{ addButtonText() }}\n </button>\n }\n <!-- Export Buttons -->\n @if (showExport()) {\n <div class=\"export-buttons\">\n <button class=\"toolbar-btn btn-export\" (click)=\"exportToExcel()\" title=\"Export to Excel/CSV\">\n \uD83D\uDCCA Excel\n </button>\n <button class=\"toolbar-btn btn-export\" (click)=\"exportToPDF()\" title=\"Export to PDF / Print\">\n \uD83D\uDCC4 PDF\n </button>\n </div>\n }\n <!-- Caption Command Template -->\n @if (captionCommandTemplate()) {\n <ng-container>\n <ng-container *ngTemplateOutlet=\"captionCommandTemplate()!\"></ng-container>\n </ng-container>\n }\n </div>\n <div class=\"toolbar-right\">\n <!-- Column Chooser -->\n @if (showColumnChooser() && (config?.reorderable || config?.resizable)) {\n <div class=\"column-chooser\">\n <button class=\"column-chooser-btn btn-icon-only\" (click)=\"showColumnChooserModal = !showColumnChooserModal\"\n title=\"Show/Hide Columns\">\n \u2630\n </button>\n <!-- Backdrop -->\n @if (showColumnChooserModal) {\n <div class=\"column-chooser-backdrop\" (click)=\"showColumnChooserModal = false\">\n </div>\n }\n @if (showColumnChooserModal) {\n <div class=\"column-chooser-panel\">\n <div class=\"column-chooser-header\">\n <span>Show/Hide Columns</span>\n <button (click)=\"showColumnChooserModal = false\">\u2715</button>\n </div>\n <div class=\"column-list\">\n @for (column of displayedColumns; track column) {\n <div class=\"column-item\">\n <label>\n <input type=\"checkbox\" [checked]=\"column.visible !== false\"\n (change)=\"toggleColumnVisibility(column)\" />\n <span>{{ column.header }}</span>\n </label>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n <theme-selector [showSelector]=\"showThemeSelector()\" />\n <!-- Refresh Button -->\n <button class=\"toolbar-btn btn-refresh btn-icon-only\" (click)=\"onRefreshClick()\" title=\"Refresh data\">\n \uD83D\uDD04\n </button>\n </div>\n </div>\n }\n\n <!-- Header -->\n @if (config?.showHeader !== false) {\n <div class=\"grid-header\" #gridHeader>\n <div class=\"grid-row header-row\">\n <!-- Checkbox Column Header -->\n @if (config?.selectable && (config?.showCheckboxColumn !== false)) {\n <div class=\"grid-cell header-cell checkbox-cell\"\n [style.width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.min-width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.max-width]=\"config?.checkboxColumnWidth || '60px'\">\n <div class=\"header-content\">\n @if (config?.multiSelect) {\n <input\n type=\"checkbox\"\n class=\"grid-checkbox header-checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleSelectAll($event)\"\n title=\"Select All\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Regular Columns -->\n @for (column of getVisibleColumns(); track column; let i = $index) {\n <div class=\"grid-cell header-cell\"\n [style.width]=\"column.width || 'auto'\" [style.min-width]=\"column.minWidth || '50px'\"\n [style.max-width]=\"column.maxWidth || 'none'\" [class.sortable]=\"column.sortable\"\n [class.dragging]=\"draggedColumn === column\" [class.drop-target]=\"dropTargetIndex === i\"\n [attr.draggable]=\"config?.reorderable\" (dragstart)=\"onDragStart($event, column, i)\"\n (dragover)=\"onColumnDragOver($event, i)\" (drop)=\"onColumnDrop($event, i)\" (dragend)=\"onColumnDragEnd()\"\n (click)=\"column.sortable && onSort(column)\">\n <div class=\"header-content\">\n <span class=\"header-title\">{{ column.header }}</span>\n <div class=\"header-actions\">\n @if (column.sortable) {\n <span class=\"sort-icon\">\n {{ getSortIcon(column) }}\n </span>\n }\n @if (column.filterable !== false && config?.filterable) {\n <button class=\"filter-btn\" [class.active]=\"isFilterActive(column)\"\n (click)=\"openFilterModal(column, $event)\" title=\"Filter\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"></polygon>\n </svg>\n </button>\n }\n </div>\n </div>\n @if (column.resizable !== false && config?.resizable) {\n <span class=\"resize-handle\" (mousedown)=\"onResizeStart($event, column)\"></span>\n }\n </div>\n }\n\n <!-- Actions Column Header -->\n @if (config?.showActions) {\n <div class=\"grid-cell header-cell actions-cell\"\n [style.width]=\"config?.actionsWidth || '150px'\"\n [style.min-width]=\"config?.actionsWidth || '150px'\"\n [style.max-width]=\"config?.actionsWidth || '150px'\">\n <div class=\"header-content\">\n <span class=\"header-title\">{{ config?.actionsHeader || 'Actions' }}</span>\n </div>\n </div>\n }\n </div>\n\n <!-- Filter Row -->\n @if (config?.filterable) {\n <div class=\"grid-row filter-row\">\n <!-- Checkbox Column Filter (Empty) -->\n @if (config?.selectable && (config?.showCheckboxColumn !== false)) {\n <div class=\"grid-cell filter-cell checkbox-cell\"\n [style.width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.min-width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.max-width]=\"config?.checkboxColumnWidth || '60px'\">\n </div>\n }\n <!-- Regular Column Filters -->\n @for (column of getVisibleColumns(); track column.field) {\n <div class=\"grid-cell filter-cell\" [style.width]=\"column.width || 'auto'\"\n [style.min-width]=\"column.minWidth || '50px'\" [style.max-width]=\"column.maxWidth || 'none'\">\n @if (column.filterable !== false) {\n <input type=\"text\" class=\"filter-input\" [placeholder]=\"'Search ' + column.header\"\n [value]=\"getFilterValue(column)\" (input)=\"onFilter(column, $any($event.target).value)\"\n (click)=\"$event.stopPropagation()\" />\n }\n </div>\n }\n <!-- Actions Column Filter (Empty) -->\n @if (config?.showActions) {\n <div class=\"grid-cell filter-cell actions-cell\"\n [style.width]=\"config?.actionsWidth || '150px'\"\n [style.min-width]=\"config?.actionsWidth || '150px'\"\n [style.max-width]=\"config?.actionsWidth || '150px'\">\n </div>\n }\n </div>\n }\n </div>\n }\n\n\n <!-- Body -->\n <div class=\"grid-body\" #gridBody>\n @for (row of gridData; track trackByRow($index, row)) {\n <div class=\"grid-row data-row\" [class.selected]=\"isRowSelected(row)\" [class.selectable]=\"config?.selectable\"\n [style.height]=\"config?.rowHeight\">\n\n <!-- Checkbox Column Cell -->\n @if (config?.selectable && (config?.showCheckboxColumn !== false)) {\n <div class=\"grid-cell data-cell checkbox-cell\"\n [style.width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.min-width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.max-width]=\"config?.checkboxColumnWidth || '60px'\">\n @if (checkboxTemplate) {\n <ng-container *ngTemplateOutlet=\"checkboxTemplate; context: { $implicit: row, selected: isRowSelected(row) }\"></ng-container>\n } @else {\n <input\n type=\"checkbox\"\n class=\"grid-checkbox row-checkbox\"\n [checked]=\"isRowSelected(row)\"\n (change)=\"onRowSelect(row, $event)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n </div>\n }\n\n <!-- Regular Data Columns -->\n @for (column of getVisibleColumns(); track column.field) {\n <div class=\"grid-cell data-cell\" [style.width]=\"column.width || 'auto'\"\n [style.min-width]=\"column.minWidth || '50px'\" [style.max-width]=\"column.maxWidth || 'none'\"\n [attr.data-type]=\"column.type\">\n @if (!column.cellTemplate) {\n {{ getCellValue(row, column) }}\n }\n @if (column.cellTemplate) {\n <ng-container\n *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: row, column: column }\"></ng-container>\n }\n </div>\n }\n\n <!-- Actions Column Cell -->\n @if (config?.showActions) {\n <div class=\"grid-cell data-cell actions-cell\"\n [style.width]=\"config?.actionsWidth || '150px'\"\n [style.min-width]=\"config?.actionsWidth || '150px'\"\n [style.max-width]=\"config?.actionsWidth || '150px'\">\n @if (actionsTemplate) {\n <ng-container *ngTemplateOutlet=\"actionsTemplate; context: { $implicit: row }\"></ng-container>\n }\n </div>\n }\n </div>\n }\n\n\n <!-- Empty State -->\n @if (gridData.length === 0 && !loading) {\n <div class=\"grid-empty\">\n <p>{{ config?.emptyMessage || 'No records found' }}</p>\n </div>\n }\n\n </div>\n\n <!-- Footer / Pagination -->\n @if (config?.showFooter !== false) {\n <div class=\"grid-footer\">\n <div class=\"pagination-info\">\n <span>\n Showing {{ pagination.page * pagination.pageSize + 1 }} to\n {{ Math.min((pagination.page + 1) * pagination.pageSize, pagination.totalRecords) }}\n of {{ pagination.totalRecords }} entries\n </span>\n </div>\n\n <div class=\"pagination-controls\">\n <!-- Page Size Selector -->\n @if (config?.pageSizeOptions; as pageSizeOptions) {\n <div class=\"page-size-selector\">\n <label>Rows per page:</label>\n <select [value]=\"pagination.pageSize\" (change)=\"onPageSizeChange(+$any($event.target).value)\"\n class=\"page-size-select\">\n @for (size of pageSizeOptions; track $index) {\n <option [value]=\"size\">\n {{ size }}\n </option>\n }\n </select>\n </div>\n }\n\n <!-- Page Navigation -->\n <div class=\"page-navigation\">\n <button class=\"page-btn\" [disabled]=\"pagination.page === 0\" (click)=\"onPageChange(0)\" title=\"First page\">\n \u27EA\n </button>\n <button class=\"page-btn\" [disabled]=\"pagination.page === 0\" (click)=\"onPageChange(pagination.page - 1)\"\n title=\"Previous page\">\n \u2039\n </button>\n\n @for (pageNum of getPageNumbers(); track pageNum) {\n @if (pageNum !== -1) {\n <button class=\"page-btn\" [class.active]=\"pageNum === pagination.page\" (click)=\"onPageChange(pageNum)\">\n {{ pageNum + 1 }}\n </button>\n }\n @if (pageNum === -1) {\n <span class=\"page-ellipsis\">...</span>\n }\n }\n\n <button class=\"page-btn\" [disabled]=\"pagination.page >= pagination.totalPages - 1\"\n (click)=\"onPageChange(pagination.page + 1)\" title=\"Next page\">\n \u203A\n </button>\n <button class=\"page-btn\" [disabled]=\"pagination.page >= pagination.totalPages - 1\"\n (click)=\"onPageChange(pagination.totalPages - 1)\" title=\"Last page\">\n \u27EB\n </button>\n </div>\n </div>\n\n <!-- Custom Footer Template -->\n @if (footerTemplate()) {\n <div class=\"grid-custom-footer\">\n <ng-container\n *ngTemplateOutlet=\"footerTemplate()!; context: { $implicit: gridData, total: pagination.totalRecords }\"></ng-container>\n </div>\n }\n </div>\n }\n\n </div>\n </div>\n\n <!-- Filter Modal -->\n @if (showFilterModal) {\n <div class=\"filter-modal-overlay\" (click)=\"closeFilterModal()\">\n <div class=\"filter-modal-content\" (click)=\"$event.stopPropagation()\">\n <div class=\"filter-modal-header\">\n <h3>Filter: {{ currentFilterColumn?.header }}</h3>\n <button class=\"close-btn\" (click)=\"closeFilterModal()\">\u00D7</button>\n </div>\n\n <div class=\"filter-modal-body\">\n <div class=\"filter-form\">\n <!-- Operator Selection - STRING -->\n @if (!currentFilterColumn?.filterType || currentFilterColumn?.filterType === 'text') {\n <div class=\"form-group\">\n <label>Operator:</label>\n <select [(ngModel)]=\"currentFilterOperator\" class=\"form-control\">\n <option value=\"contains\">Contains (\u0130\u00E7erir)</option>\n <option value=\"notContains\">Not Contains (\u0130\u00E7ermez)</option>\n <option value=\"equals\">Equals (E\u015Fittir)</option>\n <option value=\"notEquals\">Not Equals (E\u015Fit De\u011Fil)</option>\n <option value=\"startsWith\">Starts With (\u0130le Ba\u015Flar)</option>\n <option value=\"endsWith\">Ends With (\u0130le Biter)</option>\n <option value=\"isEmpty\">Is Empty (Bo\u015F)</option>\n <option value=\"isNotEmpty\">Is Not Empty (Bo\u015F De\u011Fil)</option>\n </select>\n </div>\n }\n\n <!-- Operator Selection - NUMBER -->\n @if (currentFilterColumn?.filterType === 'number') {\n <div class=\"form-group\">\n <label>Operator:</label>\n <select [(ngModel)]=\"currentFilterOperator\" class=\"form-control\">\n <option value=\"equals\">Equals (E\u015Fittir)</option>\n <option value=\"notEquals\">Not Equals (E\u015Fit De\u011Fil)</option>\n <option value=\"lt\">Less Than (K\u00FC\u00E7\u00FCkt\u00FCr)</option>\n <option value=\"lte\">Less Than or Equal (K\u00FC\u00E7\u00FCk E\u015Fit)</option>\n <option value=\"gt\">Greater Than (B\u00FCy\u00FCkt\u00FCr)</option>\n <option value=\"gte\">Greater Than or Equal (B\u00FCy\u00FCk E\u015Fit)</option>\n <option value=\"isEmpty\">Is Empty (Bo\u015F)</option>\n <option value=\"isNotEmpty\">Is Not Empty (Bo\u015F De\u011Fil)</option>\n </select>\n </div>\n }\n\n <!-- Operator Selection - DATE -->\n @if (currentFilterColumn?.filterType === 'date') {\n <div class=\"form-group\">\n <label>Operator:</label>\n <select [(ngModel)]=\"currentFilterOperator\" class=\"form-control\">\n <option value=\"equals\">On Date (Tarihte)</option>\n <option value=\"notEquals\">Not On Date (Tarihte De\u011Fil)</option>\n <option value=\"lt\">Before (\u00D6nce)</option>\n <option value=\"lte\">On or Before (Veya \u00D6nce)</option>\n <option value=\"gt\">After (Sonra)</option>\n <option value=\"gte\">On or After (Veya Sonra)</option>\n <option value=\"isEmpty\">Is Empty (Bo\u015F)</option>\n <option value=\"isNotEmpty\">Is Not Empty (Bo\u015F De\u011Fil)</option>\n </select>\n </div>\n }\n\n <!-- Value Input - TEXT -->\n @if ((!currentFilterColumn?.filterType || currentFilterColumn?.filterType === 'text') && currentFilterOperator\n !== 'isEmpty' && currentFilterOperator !== 'isNotEmpty') {\n <div class=\"form-group\">\n <label>Value:</label>\n <input type=\"text\" [(ngModel)]=\"filterValue\" class=\"form-control\" placeholder=\"Enter text...\"\n (keyup.enter)=\"applyFilterModal()\" />\n </div>\n }\n\n <!-- Value Input - NUMBER -->\n @if (currentFilterColumn?.filterType === 'number' && currentFilterOperator !== 'isEmpty' &&\n currentFilterOperator !== 'isNotEmpty') {\n <div class=\"form-group\">\n <label>Value:</label>\n <input type=\"number\" [(ngModel)]=\"filterValue\" class=\"form-control\" placeholder=\"Enter number...\"\n (keyup.enter)=\"applyFilterModal()\" />\n </div>\n }\n\n <!-- Value Input - DATE -->\n @if (currentFilterColumn?.filterType === 'date' && currentFilterOperator !== 'isEmpty' && currentFilterOperator\n !== 'isNotEmpty') {\n <div class=\"form-group\">\n <label>Value:</label>\n <input type=\"date\" [(ngModel)]=\"filterValue\" class=\"form-control\" (keyup.enter)=\"applyFilterModal()\" />\n </div>\n }\n\n\n @if (currentFilterOperator === 'isEmpty' || currentFilterOperator === 'isNotEmpty') {\n <div class=\"form-group\">\n <p\n style=\"padding: 12px; background: var(--grid-bg-secondary); border-radius: var(--grid-radius-sm); color: var(--grid-text-secondary); font-size: 0.875rem;\">\n \u2139\uFE0F Bu operat\u00F6r i\u00E7in de\u011Fer girmeye gerek yok.\n </p>\n </div>\n }\n\n </div>\n </div>\n\n <div class=\"filter-modal-footer\">\n <button class=\"btn-cancel\" (click)=\"closeFilterModal()\">Cancel</button>\n <button class=\"btn-clear\" (click)=\"clearFilterModal()\">Clear Filter</button>\n <button class=\"btn-apply\" (click)=\"applyFilterModal()\">Apply</button>\n </div>\n </div>\n </div>\n }", styles: ["@charset \"UTF-8\";:root{--grid-primary-color: #4096ff;--grid-primary-hover: #1677ff;--grid-primary-light: rgba(64, 150, 255, .1);--grid-bg-primary: #ffffff;--grid-bg-secondary: #fafafa;--grid-bg-hover: #f5f9ff;--grid-bg-selected: #e6f4ff;--grid-bg-selected-hover: #d9ecff;--grid-text-primary: #262626;--grid-text-secondary: #595959;--grid-text-tertiary: #8c8c8c;--grid-text-disabled: #bfbfbf;--grid-text-placeholder: #bfbfbf;--grid-border-color: #d9d9d9;--grid-border-light: #e8e8e8;--grid-border-dark: #e0e0e0;--grid-shadow-sm: 0 2px 8px rgba(0, 0, 0, .1);--grid-shadow-md: 0 4px 12px rgba(0, 0, 0, .15);--grid-shadow-lg: 0 6px 16px rgba(0, 0, 0, .12);--grid-shadow-color: rgba(0, 0, 0, .1);--grid-success-color: #52c41a;--grid-warning-color: #faad14;--grid-error-color: #ff4d4f;--grid-info-color: #1890ff;--grid-transition: all .3s ease;--grid-transition-fast: all .2s ease;--grid-transition-normal: all .3s ease;--grid-radius-sm: 4px;--grid-radius-md: 6px;--grid-radius-lg: 8px;--grid-header-bg: linear-gradient(180deg, #fafafa 0%, #f5f5f5 100%);--grid-footer-bg: #fafafa}[data-theme=dark],.dark-theme{--grid-primary-color: #1890ff;--grid-primary-hover: #40a9ff;--grid-primary-light: rgba(24, 144, 255, .2);--grid-bg-primary: #1a1a1a;--grid-bg-secondary: #0a0a0a;--grid-bg-hover: #2a2a2a;--grid-bg-selected: #111b26;--grid-bg-selected-hover: #0d1520;--grid-text-primary: #e8e8e8;--grid-text-secondary: #bfbfbf;--grid-text-tertiary: #8c8c8c;--grid-text-disabled: #595959;--grid-text-placeholder: #595959;--grid-border-color: #434343;--grid-border-light: #303030;--grid-border-dark: #4a4a4a;--grid-shadow-sm: 0 2px 8px rgba(0, 0, 0, .45);--grid-shadow-md: 0 4px 12px rgba(0, 0, 0, .55);--grid-shadow-lg: 0 6px 16px rgba(0, 0, 0, .65);--grid-shadow-color: rgba(0, 0, 0, .5);--grid-success-color: #49aa19;--grid-warning-color: #d89614;--grid-error-color: #d32029;--grid-info-color: #177ddc;--grid-header-bg: linear-gradient(180deg, #1f1f1f 0%, #1a1a1a 100%);--grid-footer-bg: #1f1f1f}*{transition:background-color .3s ease,color .3s ease,border-color .3s ease}body{background-color:var(--grid-bg-primary);color:var(--grid-text-primary);transition:background-color .3s ease,color .3s ease}.flexi-data-grid{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:var(--grid-text-primary);background:var(--grid-bg-primary);border-radius:var(--grid-radius-lg);box-shadow:var(--grid-shadow-sm);overflow:hidden}.flexi-data-grid.loading{pointer-events:none}.grid-loading-overlay{position:absolute;inset:0;background:var(--grid-bg-primary);opacity:.95;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:1000;gap:12px}.grid-loading-overlay .spinner{width:40px;height:40px;border:4px solid var(--grid-border-light);border-top:4px solid var(--grid-primary-color);border-radius:50%;animation:spin 1s cubic-bezier(.5,0,.5,1) infinite}.grid-loading-overlay:after{content:\"Loading...\";color:var(--grid-text-secondary);font-size:14px;font-weight:500}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.grid-container{display:flex;flex-direction:column;width:100%;min-height:400px;position:relative}.grid-toolbar{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--grid-header-bg);border-bottom:1px solid var(--grid-border-dark);gap:16px;flex-wrap:wrap}.grid-toolbar .toolbar-left{display:flex;flex:0 0 auto;align-items:center;gap:12px;flex-wrap:wrap}.grid-toolbar .toolbar-center{display:flex;flex:1 1 auto;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap}.grid-toolbar .toolbar-right{display:flex;flex:0 0 auto;align-items:center;gap:4px;flex-wrap:wrap}.global-search{flex:1;min-width:200px;max-width:400px}.global-search .global-search-input{width:100%;padding:8px 12px;border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-md);font-size:14px;background:var(--grid-bg-primary);color:var(--grid-text-primary);transition:var(--grid-transition-fast)}.global-search .global-search-input:focus{outline:none;border-color:var(--grid-primary-color);box-shadow:0 0 0 2px var(--grid-primary-light)}.global-search .global-search-input::placeholder{color:var(--grid-text-placeholder)}.toolbar-btn{padding:8px 16px;border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-md);font-size:13px;font-weight:500;cursor:pointer;background:var(--grid-bg-primary);color:var(--grid-text-primary);transition:var(--grid-transition-fast);display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.toolbar-btn:hover{border-color:var(--grid-primary-color);color:var(--grid-primary-color);transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.toolbar-btn:active{transform:translateY(0)}.toolbar-btn.btn-add{background:var(--grid-success-color);color:#fff;border-color:var(--grid-success-color)}.toolbar-btn.btn-add:hover{opacity:.9;color:#fff}.toolbar-btn.btn-export:hover{background:var(--grid-primary-light)}.toolbar-btn.btn-icon-only{padding:10px;font-size:20px;line-height:1}.toolbar-btn.btn-icon-only:hover{background:var(--grid-primary-light)}.export-buttons{display:flex;gap:8px}.column-chooser{position:relative;padding:12px 16px;background:var(--grid-header-bg);border-bottom:1px solid var(--grid-border-dark)}.column-chooser .column-chooser-btn{padding:8px 16px;background:var(--grid-bg-primary);border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-md);cursor:pointer;font-size:13px;font-weight:500;color:var(--grid-text-primary);transition:var(--grid-transition-fast);display:inline-flex;align-items:center;gap:6px;box-shadow:var(--grid-shadow-sm)}.column-chooser .column-chooser-btn.btn-icon-only{padding:10px;font-size:20px;line-height:1}.column-chooser .column-chooser-btn:hover{border-color:var(--grid-primary-color);color:var(--grid-primary-color);box-shadow:0 2px 4px var(--grid-primary-light);transform:translateY(-1px)}.column-chooser .column-chooser-btn:active{transform:translateY(0);box-shadow:0 1px 2px #0000000d}.column-chooser .column-chooser-backdrop{position:fixed;inset:0;background:transparent;z-index:999}.column-chooser .column-chooser-panel{position:absolute;top:calc(100% + 4px);left:16px;margin-top:4px;background:var(--grid-bg-primary);border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-lg);box-shadow:var(--grid-shadow-lg);z-index:1000;min-width:280px;max-width:400px;animation:slideDown .2s ease}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.column-chooser .column-chooser-panel .column-chooser-header{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid var(--grid-border-light);font-weight:600;font-size:14px;color:var(--grid-text-primary);background:var(--grid-header-bg);border-radius:var(--grid-radius-lg) var(--grid-radius-lg) 0 0}.column-chooser .column-chooser-panel .column-chooser-header button{background:none;border:none;cursor:pointer;font-size:18px;padding:0;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:var(--grid-radius-md);color:var(--grid-text-tertiary);transition:var(--grid-transition-fast)}.column-chooser .column-chooser-panel .column-chooser-header button:hover{background:var(--grid-bg-hover);color:var(--grid-text-primary)}.column-chooser .column-chooser-panel .column-chooser-header button:active{background:var(--grid-bg-selected)}.column-chooser .column-chooser-panel .column-list{max-height:400px;overflow-y:auto;padding:8px 0;display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.column-chooser .column-chooser-panel .column-item{padding:10px 16px;transition:var(--grid-transition-fast);margin:0 8px;border-radius:var(--grid-radius-md)}.column-chooser .column-chooser-panel .column-item:hover{background:var(--grid-bg-hover)}.column-chooser .column-chooser-panel .column-item label{display:flex;align-items:center;gap:10px;cursor:pointer;margin:0;-webkit-user-select:none;user-select:none}.column-chooser .column-chooser-panel .column-item label input[type=checkbox]{cursor:pointer;width:16px;height:16px;accent-color:var(--grid-primary-color)}.column-chooser .column-chooser-panel .column-item label span{flex:1;font-size:13px;color:var(--grid-text-primary)}.grid-row{display:flex;width:100%;border-bottom:1px solid var(--grid-border-light);min-width:fit-content}.grid-row:last-child{border-bottom:none}.grid-cell{flex:1 1 0;min-width:0;padding:12px 16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;box-sizing:border-box}.grid-cell[style*=width]{flex:0 0 auto}.grid-header{background:var(--grid-header-bg);border-bottom:2px solid var(--grid-border-dark);overflow-x:auto;overflow-y:hidden}.grid-header::-webkit-scrollbar{display:none}.grid-header{-ms-overflow-style:none;scrollbar-width:none}.grid-header .header-row{border-bottom:none}.grid-header .header-cell{font-weight:600;color:var(--grid-text-primary);cursor:default;-webkit-user-select:none;user-select:none;position:relative;transition:var(--grid-transition-fast)}.grid-header .header-cell.sortable{cursor:pointer}.grid-header .header-cell.sortable:hover{background-color:var(--grid-bg-hover)}.grid-header .header-cell.dragging{opacity:.4;background-color:var(--grid-primary-light);cursor:grabbing!important;transform:scale(.98);box-shadow:var(--grid-shadow-md)}.grid-header .header-cell.drop-target{border-left:4px solid var(--grid-primary-color);background-color:var(--grid-primary-light)}.grid-header .header-cell.drop-target:before{content:\"\";position:absolute;left:-4px;top:0;bottom:0;width:4px;background:var(--grid-primary-color);box-shadow:0 0 8px var(--grid-primary-light)}.grid-header .header-cell[draggable=true]{cursor:grab;transition:var(--grid-transition-fast)}.grid-header .header-cell[draggable=true]:hover{background-color:var(--grid-primary-light)}.grid-header .header-cell[draggable=true]:active{cursor:grabbing}.grid-header .header-cell.resizing{-webkit-user-select:none;user-select:none;cursor:col-resize;background-color:var(--grid-primary-light)}.grid-header .header-cell.resizing .resize-handle{opacity:1;background-color:var(--grid-primary-light)}.grid-header .header-cell.resizing .resize-handle:after{background-color:var(--grid-primary-color);box-shadow:0 0 8px var(--grid-primary-light);width:4px}.grid-header .header-cell .header-content{display:flex;align-items:center;justify-content:space-between;flex:1;gap:8px}.grid-header .header-cell .header-title{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.grid-header .header-cell .header-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.grid-header .header-cell .sort-icon{color:var(--grid-text-tertiary);font-size:14px;font-weight:700;transition:var(--grid-transition-fast);opacity:.7}.header-cell.sortable:hover .grid-header .header-cell .sort-icon{opacity:1;color:var(--grid-primary-color)}.grid-header .header-cell .resize-handle{position:absolute;right:-6px;top:0;bottom:0;width:16px;cursor:col-resize;-webkit-user-select:none;user-select:none;z-index:10;display:flex;align-items:center;justify-content:center;opacity:.7;transition:var(--grid-transition-fast)}.grid-header .header-cell .resize-handle:hover{opacity:1;background-color:var(--grid-primary-light)}.grid-header .header-cell .resize-handle:before{content:\"\\22ee\";color:var(--grid-text-secondary);font-size:18px;font-weight:700;line-height:1;text-shadow:0 1px 2px var(--grid-shadow-color)}.grid-header .header-cell .resize-handle:hover:before{color:var(--grid-primary-color)}.grid-header .header-cell .resize-handle:after{content:\"\";position:absolute;right:6px;top:10%;bottom:10%;width:3px;background-color:var(--grid-border-color);border-radius:var(--grid-radius-sm);transition:var(--grid-transition-fast)}.grid-header .header-cell .resize-handle:hover:after{background-color:var(--grid-primary-color);box-shadow:0 0 4px var(--grid-primary-light)}.grid-header .filter-row{background:var(--grid-bg-secondary);border-top:1px solid var(--grid-border-light)}.grid-header .filter-row .filter-cell{padding:6px 10px}.grid-header .filter-row .filter-input{width:100%;padding:8px 12px;border:1px solid var(--grid-border-light);border-radius:var(--grid-radius-sm);font-size:13px;transition:all .2s ease;background-color:var(--grid-bg-primary);color:var(--grid-text-primary)}.grid-header .filter-row .filter-input:hover{border-color:var(--grid-primary-color)}.grid-header .filter-row .filter-input:focus{outline:none;border-color:var(--grid-primary-color);box-shadow:0 0 0 3px #4f46e51a}.grid-header .filter-row .filter-input::placeholder{color:var(--grid-text-tertiary);font-size:12px;opacity:.7}.grid-body{flex:1;overflow-x:auto;overflow-y:auto;min-height:200px;background:var(--grid-bg-primary)}.grid-body .data-row{transition:var(--grid-transition-fast);border-left:3px solid transparent}.grid-body .data-row:hover{background-color:var(--grid-bg-hover);border-left-color:var(--grid-primary-light);box-shadow:inset 0 0 0 1px var(--grid-primary-light)}.grid-body .data-row.selectable{cursor:pointer}.grid-body .data-row.selectable:active{background-color:var(--grid-bg-selected)}.grid-body .data-row.selected{background-color:var(--grid-bg-selected);border-left-color:var(--grid-primary-color)}.grid-body .data-row.selected:hover{background-color:var(--grid-bg-selected-hover)}.grid-body .data-row .data-cell{color:var(--grid-text-primary);font-size:13px;transition:var(--grid-transition-fast)}.grid-body .data-row .data-cell[data-type=number]{justify-content:flex-end;text-align:right;font-variant-numeric:tabular-nums}.grid-body .data-row .data-cell[data-type=boolean]{justify-content:center}.grid-body .data-row .data-cell[data-type=date]{font-variant-numeric:tabular-nums}.grid-body .grid-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:300px;color:var(--grid-text-tertiary);font-size:14px;padding:40px 20px}.grid-body .grid-empty:before{content:\"\\1f4c2\";font-size:48px;margin-bottom:16px;opacity:.5}.grid-body .grid-empty p{margin:0;font-weight:500}.grid-footer{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--grid-header-bg);border-top:1px solid var(--grid-border-light);flex-wrap:wrap;gap:12px}.grid-footer .pagination-info{font-size:13px;color:var(--grid-text-secondary)}.grid-footer .pagination-controls{display:flex;align-items:center;gap:16px}.grid-footer .page-size-selector{display:flex;align-items:center;gap:8px;font-size:13px}.grid-footer .page-size-selector label{color:var(--grid-text-secondary);margin:0}.grid-footer .page-size-selector .page-size-select{padding:4px 8px;border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-md);font-size:13px;cursor:pointer;transition:var(--grid-transition-normal);background-color:var(--grid-bg-primary);color:var(--grid-text-primary)}.grid-footer .page-size-selector .page-size-select:focus{outline:none;border-color:var(--grid-primary-color);box-shadow:0 0 0 2px var(--grid-primary-light)}.grid-footer .page-navigation{display:flex;gap:4px}.grid-footer .page-navigation .page-btn{min-width:32px;height:32px;padding:0 8px;border:1px solid var(--grid-border-color);background:var(--grid-bg-primary);color:var(--grid-text-secondary);font-size:13px;border-radius:var(--grid-radius-md);cursor:pointer;transition:var(--grid-transition-normal);display:flex;align-items:center;justify-content:center}.grid-footer .page-navigation .page-btn:hover:not(:disabled){border-color:var(--grid-primary-color);color:var(--grid-primary-color)}.grid-footer .page-navigation .page-btn.active{background:var(--grid-primary-color);color:#fff;border-color:var(--grid-primary-color);font-weight:600}.grid-footer .page-navigation .page-btn:disabled{cursor:not-allowed;opacity:.5}.grid-footer .page-navigation .page-ellipsis{display:flex;align-items:center;padding:0 4px;color:var(--grid-text-tertiary)}.grid-custom-footer{padding:16px;background:var(--grid-bg-secondary);border-top:2px solid var(--grid-border-color);color:var(--grid-text-primary)}@media(max-width:768px){.grid-footer{flex-direction:column;align-items:stretch}.grid-footer .pagination-info{text-align:center}.grid-footer .pagination-controls{flex-direction:column;align-items:stretch}.grid-footer .page-navigation{justify-content:center}}.filter-modal-overlay{position:fixed;inset:0;background:#0009;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:1000;animation:fadeIn .25s ease-out}.filter-modal-content{background:var(--grid-bg-primary);border-radius:var(--grid-radius-lg);box-shadow:0 20px 60px #00000059;width:90%;max-width:450px;max-height:90vh;overflow:hidden;animation:slideIn .3s cubic-bezier(.34,1.56,.64,1);border:1px solid var(--grid-border-light)}.filter-modal-header{padding:20px 24px;border-bottom:1px solid var(--grid-border-light);display:flex;align-items:center;justify-content:space-between;background:linear-gradient(135deg,var(--grid-primary-color) 0%,#6366f1 100%)}.filter-modal-header h3{margin:0;font-size:1.125rem;font-weight:600;color:#fff;display:flex;align-items:center;gap:8px}.filter-modal-header h3:before{content:\"\\1f50d\";font-size:1.2rem}.filter-modal-header .close-btn{background:#fff3;border:none;font-size:1.5rem;color:#fff;cursor:pointer;padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--grid-radius-sm);transition:all .2s ease}.filter-modal-header .close-btn:hover{background:#ffffff4d;transform:rotate(90deg)}.filter-modal-body{padding:24px}.filter-modal-body .form-group{margin-bottom:20px}.filter-modal-body .form-group label{display:block;margin-bottom:8px;font-weight:600;color:var(--grid-text-primary);font-size:.875rem;text-transform:uppercase;letter-spacing:.5px}.filter-modal-body .form-group .form-control{width:100%;padding:12px 14px;border:2px solid var(--grid-border-light);border-radius:var(--grid-radius-sm);background:var(--grid-bg-primary);color:var(--grid-text-primary);font-size:.9375rem;transition:all .2s ease}.filter-modal-body .form-group .form-control:focus{outline:none;border-color:var(--grid-primary-color);box-shadow:0 0 0 3px #4f46e51f;background:var(--grid-bg-primary)}.filter-modal-body .form-group .form-control:hover:not(:focus){border-color:var(--grid-border-dark)}.filter-modal-body .form-group select.form-control{cursor:pointer;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%234f46e5' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 12px center;padding-right:36px;appearance:none}.filter-modal-body .form-group input.form-control::placeholder{color:var(--grid-text-tertiary);opacity:.6}.filter-modal-footer{padding:16px 20px;border-top:1px solid var(--grid-border-light);display:flex;gap:10px;justify-content:flex-end;background:var(--grid-bg-secondary);border-radius:0 0 var(--grid-radius-lg) var(--grid-radius-lg)}.filter-modal-footer button{padding:10px 20px;border-radius:var(--grid-radius-sm);font-weight:500;font-size:.875rem;cursor:pointer;transition:all .2s ease;border:none;min-width:80px}.filter-modal-footer button.btn-cancel{background:var(--grid-bg-primary);color:var(--grid-text-primary);border:1px solid var(--grid-border-light)}.filter-modal-footer button.btn-cancel:hover{background:var(--grid-bg-hover);border-color:var(--grid-border-dark)}.filter-modal-footer button.btn-clear{background:transparent;color:#ef4444;border:1px solid #ef4444}.filter-modal-footer button.btn-clear:hover{background:#ef44441a;border-color:#dc2626;color:#dc2626}.filter-modal-footer button.btn-apply{background:var(--grid-primary-color);color:#fff;border:1px solid var(--grid-primary-color)}.filter-modal-footer button.btn-apply:hover{background:#4338ca;transform:translateY(-1px);box-shadow:0 4px 12px #4f46e54d}.filter-modal-footer button.btn-apply:disabled{opacity:.5;cursor:not-allowed;transform:none}.filter-btn{background:transparent;border:none;cursor:pointer;padding:4px 6px;border-radius:var(--grid-radius-sm);font-size:.875rem;color:var(--grid-text-tertiary);transition:all .2s ease;opacity:.7;line-height:1;display:flex;align-items:center;justify-content:center}.filter-btn:hover{background:var(--grid-bg-hover);opacity:1;color:var(--grid-text-primary);transform:scale(1.1)}.filter-btn.active{color:var(--grid-primary-color);opacity:1;background:#4f46e51f}.filter-btn.active:hover{background:#4f46e52e}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideIn{0%{opacity:0;transform:translateY(-30px) scale(.9)}to{opacity:1;transform:translateY(0) scale(1)}}.grid-body::-webkit-scrollbar{width:8px;height:8px}.grid-body::-webkit-scrollbar-track{background:var(--grid-bg-secondary)}.grid-body::-webkit-scrollbar-thumb{background:var(--grid-border-dark);border-radius:var(--grid-radius-md)}.grid-body::-webkit-scrollbar-thumb:hover{background:var(--grid-text-tertiary)}.checkbox-cell{display:flex;align-items:center;justify-content:center;padding:8px!important;flex:0 0 auto!important}.checkbox-cell .grid-checkbox{position:relative;appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer;width:20px;height:20px;border:2px solid var(--grid-border-dark);border-radius:var(--grid-radius-sm);background:var(--grid-bg-primary);transition:all .25s cubic-bezier(.4,0,.2,1);outline:none}.checkbox-cell .grid-checkbox:hover{border-color:var(--grid-primary-color);background:var(--grid-bg-hover);transform:scale(1.05);box-shadow:0 0 0 4px #4096ff14}.checkbox-cell .grid-checkbox:focus,.checkbox-cell .grid-checkbox:focus-visible{border-color:var(--grid-primary-color);box-shadow:0 0 0 4px #4096ff26}.checkbox-cell .grid-checkbox:checked{background:var(--grid-primary-color);border-color:var(--grid-primary-color)}.checkbox-cell .grid-checkbox:checked:before{content:\"\";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) rotate(45deg) scale(1);width:5px;height:10px;border:solid white;border-width:0 2px 2px 0;opacity:1}.checkbox-cell .grid-checkbox:checked:hover{background:var(--grid-primary-hover, #1677ff);border-color:var(--grid-primary-hover, #1677ff)}.checkbox-cell .grid-checkbox:indeterminate{background:var(--grid-primary-color);border-color:var(--grid-primary-color)}.checkbox-cell .grid-checkbox:indeterminate:before{content:\"\";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:10px;height:2px;background:#fff;border-radius:1px}.checkbox-cell .grid-checkbox:disabled{cursor:not-allowed;opacity:.5;background:var(--grid-bg-secondary);border-color:var(--grid-border-light)}.checkbox-cell .grid-checkbox:disabled:hover{transform:none;box-shadow:none}.checkbox-cell .grid-checkbox:before{transition:all .2s cubic-bezier(.4,0,.2,1);opacity:0}.checkbox-cell .header-checkbox{width:20px;height:20px;border-width:2px}.checkbox-cell .header-checkbox:checked:before{width:5px;height:10px;border-width:0 2.5px 2.5px 0}.checkbox-cell .row-checkbox{width:18px;height:18px}.checkbox-cell .row-checkbox:checked:before{width:4px;height:9px;border-width:0 2px 2px 0}.actions-cell{display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 12px!important;flex:0 0 auto!important}.actions-cell button{padding:6px 12px;border:1px solid var(--grid-border-dark);background:var(--grid-bg-primary);color:var(--grid-text-primary);border-radius:var(--grid-radius-sm);cursor:pointer;font-size:13px;font-weight:500;transition:var(--grid-transition-fast);white-space:nowrap}.actions-cell button:hover{background:var(--grid-bg-hover);border-color:var(--grid-primary-color);color:var(--grid-primary-color);transform:translateY(-1px)}.actions-cell button:active{transform:translateY(0)}.actions-cell button:focus{outline:2px solid var(--grid-primary-color);outline-offset:2px}.actions-cell button.btn-edit{color:var(--grid-primary-color);border-color:var(--grid-primary-color)}.actions-cell button.btn-edit:hover{background:var(--grid-primary-color);color:#fff}.actions-cell button.btn-delete{color:#ef4444;border-color:#ef4444}.actions-cell button.btn-delete:hover{background:#ef4444;color:#fff}.actions-cell button.btn-view{color:#10b981;border-color:#10b981}.actions-cell button.btn-view:hover{background:#10b981;color:#fff}.actions-cell button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.actions-cell .icon-btn{padding:6px;min-width:32px;display:flex;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i4.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i4.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: ThemeSelectorComponent, selector: "theme-selector", inputs: ["showSelector"] }] });
1671
+ }
1672
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: DataGridComponent, decorators: [{
1673
+ type: Component,
1674
+ args: [{ selector: 'nlabs-data-grid', standalone: true, imports: [
1675
+ CommonModule,
1676
+ FormsModule,
1677
+ GridColumnComponent,
1678
+ ThemeSelectorComponent
1679
+ ], providers: [GridDataService, ThemeService], template: "<div class=\"nlabs-data-grid\" [class.loading]=\"loading\">\n <!-- Loading Overlay -->\n @if (loading) {\n <div class=\"grid-loading-overlay\">\n <div class=\"spinner\"></div>\n </div>\n }\n\n\n <!-- Grid Container -->\n <div class=\"grid-container\">\n <!-- Toolbar with Global Search, Export, Theme Selector -->\n @if (config?.showHeader !== false) {\n <div class=\"grid-toolbar\">\n <div class=\"toolbar-left\">\n <!-- Global Search -->\n @if(showGlobalSearch()){\n <div class=\"global-search\">\n <input type=\"text\" class=\"global-search-input\" placeholder=\"\uD83D\uDD0D Search in all columns...\"\n [(ngModel)]=\"globalSearchTerm\" (input)=\"onGlobalSearch($any($event.target).value)\" />\n </div>\n }\n </div>\n <div class=\"toolbar-center\">\n <!-- Add Button -->\n @if (showAddButton()) {\n <button class=\"toolbar-btn btn-add\" (click)=\"onAddButtonClick()\" title=\"Add new record\">\n \u2795 {{ addButtonText() }}\n </button>\n }\n <!-- Export Buttons -->\n @if (showExport()) {\n <div class=\"export-buttons\">\n <button class=\"toolbar-btn btn-export\" (click)=\"exportToExcel()\" title=\"Export to Excel/CSV\">\n \uD83D\uDCCA Excel\n </button>\n <button class=\"toolbar-btn btn-export\" (click)=\"exportToPDF()\" title=\"Export to PDF / Print\">\n \uD83D\uDCC4 PDF\n </button>\n </div>\n }\n <!-- Caption Command Template -->\n @if (captionCommandTemplate()) {\n <ng-container>\n <ng-container *ngTemplateOutlet=\"captionCommandTemplate()!\"></ng-container>\n </ng-container>\n }\n </div>\n <div class=\"toolbar-right\">\n <!-- Column Chooser -->\n @if (showColumnChooser() && (config?.reorderable || config?.resizable)) {\n <div class=\"column-chooser\">\n <button class=\"column-chooser-btn btn-icon-only\" (click)=\"showColumnChooserModal = !showColumnChooserModal\"\n title=\"Show/Hide Columns\">\n \u2630\n </button>\n <!-- Backdrop -->\n @if (showColumnChooserModal) {\n <div class=\"column-chooser-backdrop\" (click)=\"showColumnChooserModal = false\">\n </div>\n }\n @if (showColumnChooserModal) {\n <div class=\"column-chooser-panel\">\n <div class=\"column-chooser-header\">\n <span>Show/Hide Columns</span>\n <button (click)=\"showColumnChooserModal = false\">\u2715</button>\n </div>\n <div class=\"column-list\">\n @for (column of displayedColumns; track column) {\n <div class=\"column-item\">\n <label>\n <input type=\"checkbox\" [checked]=\"column.visible !== false\"\n (change)=\"toggleColumnVisibility(column)\" />\n <span>{{ column.header }}</span>\n </label>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n <theme-selector [showSelector]=\"showThemeSelector()\" />\n <!-- Refresh Button -->\n <button class=\"toolbar-btn btn-refresh btn-icon-only\" (click)=\"onRefreshClick()\" title=\"Refresh data\">\n \uD83D\uDD04\n </button>\n </div>\n </div>\n }\n\n <!-- Header -->\n @if (config?.showHeader !== false) {\n <div class=\"grid-header\" #gridHeader>\n <div class=\"grid-row header-row\">\n <!-- Checkbox Column Header -->\n @if (config?.selectable && (config?.showCheckboxColumn !== false)) {\n <div class=\"grid-cell header-cell checkbox-cell\"\n [style.width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.min-width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.max-width]=\"config?.checkboxColumnWidth || '60px'\">\n <div class=\"header-content\">\n @if (config?.multiSelect) {\n <input\n type=\"checkbox\"\n class=\"grid-checkbox header-checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleSelectAll($event)\"\n title=\"Select All\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Regular Columns -->\n @for (column of getVisibleColumns(); track column; let i = $index) {\n <div class=\"grid-cell header-cell\"\n [style.width]=\"column.width || 'auto'\" [style.min-width]=\"column.minWidth || '50px'\"\n [style.max-width]=\"column.maxWidth || 'none'\" [class.sortable]=\"column.sortable\"\n [class.dragging]=\"draggedColumn === column\" [class.drop-target]=\"dropTargetIndex === i\"\n [attr.draggable]=\"config?.reorderable\" (dragstart)=\"onDragStart($event, column, i)\"\n (dragover)=\"onColumnDragOver($event, i)\" (drop)=\"onColumnDrop($event, i)\" (dragend)=\"onColumnDragEnd()\"\n (click)=\"column.sortable && onSort(column)\">\n <div class=\"header-content\">\n <span class=\"header-title\">{{ column.header }}</span>\n <div class=\"header-actions\">\n @if (column.sortable) {\n <span class=\"sort-icon\">\n {{ getSortIcon(column) }}\n </span>\n }\n @if (column.filterable !== false && config?.filterable) {\n <button class=\"filter-btn\" [class.active]=\"isFilterActive(column)\"\n (click)=\"openFilterModal(column, $event)\" title=\"Filter\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"></polygon>\n </svg>\n </button>\n }\n </div>\n </div>\n @if (column.resizable !== false && config?.resizable) {\n <span class=\"resize-handle\" (mousedown)=\"onResizeStart($event, column)\"></span>\n }\n </div>\n }\n\n <!-- Actions Column Header -->\n @if (config?.showActions) {\n <div class=\"grid-cell header-cell actions-cell\"\n [style.width]=\"config?.actionsWidth || '150px'\"\n [style.min-width]=\"config?.actionsWidth || '150px'\"\n [style.max-width]=\"config?.actionsWidth || '150px'\">\n <div class=\"header-content\">\n <span class=\"header-title\">{{ config?.actionsHeader || 'Actions' }}</span>\n </div>\n </div>\n }\n </div>\n\n <!-- Filter Row -->\n @if (config?.filterable) {\n <div class=\"grid-row filter-row\">\n <!-- Checkbox Column Filter (Empty) -->\n @if (config?.selectable && (config?.showCheckboxColumn !== false)) {\n <div class=\"grid-cell filter-cell checkbox-cell\"\n [style.width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.min-width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.max-width]=\"config?.checkboxColumnWidth || '60px'\">\n </div>\n }\n <!-- Regular Column Filters -->\n @for (column of getVisibleColumns(); track column.field) {\n <div class=\"grid-cell filter-cell\" [style.width]=\"column.width || 'auto'\"\n [style.min-width]=\"column.minWidth || '50px'\" [style.max-width]=\"column.maxWidth || 'none'\">\n @if (column.filterable !== false) {\n <input type=\"text\" class=\"filter-input\" [placeholder]=\"'Search ' + column.header\"\n [value]=\"getFilterValue(column)\" (input)=\"onFilter(column, $any($event.target).value)\"\n (click)=\"$event.stopPropagation()\" />\n }\n </div>\n }\n <!-- Actions Column Filter (Empty) -->\n @if (config?.showActions) {\n <div class=\"grid-cell filter-cell actions-cell\"\n [style.width]=\"config?.actionsWidth || '150px'\"\n [style.min-width]=\"config?.actionsWidth || '150px'\"\n [style.max-width]=\"config?.actionsWidth || '150px'\">\n </div>\n }\n </div>\n }\n </div>\n }\n\n\n <!-- Body -->\n <div class=\"grid-body\" #gridBody>\n @for (row of gridData; track trackByRow($index, row)) {\n <div class=\"grid-row data-row\" [class.selected]=\"isRowSelected(row)\" [class.selectable]=\"config?.selectable\"\n [style.height]=\"config?.rowHeight\">\n\n <!-- Checkbox Column Cell -->\n @if (config?.selectable && (config?.showCheckboxColumn !== false)) {\n <div class=\"grid-cell data-cell checkbox-cell\"\n [style.width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.min-width]=\"config?.checkboxColumnWidth || '60px'\"\n [style.max-width]=\"config?.checkboxColumnWidth || '60px'\">\n @if (checkboxTemplate) {\n <ng-container *ngTemplateOutlet=\"checkboxTemplate; context: { $implicit: row, selected: isRowSelected(row) }\"></ng-container>\n } @else {\n <input\n type=\"checkbox\"\n class=\"grid-checkbox row-checkbox\"\n [checked]=\"isRowSelected(row)\"\n (change)=\"onRowSelect(row, $event)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n </div>\n }\n\n <!-- Regular Data Columns -->\n @for (column of getVisibleColumns(); track column.field) {\n <div class=\"grid-cell data-cell\" [style.width]=\"column.width || 'auto'\"\n [style.min-width]=\"column.minWidth || '50px'\" [style.max-width]=\"column.maxWidth || 'none'\"\n [attr.data-type]=\"column.type\">\n @if (!column.cellTemplate) {\n {{ getCellValue(row, column) }}\n }\n @if (column.cellTemplate) {\n <ng-container\n *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: row, column: column }\"></ng-container>\n }\n </div>\n }\n\n <!-- Actions Column Cell -->\n @if (config?.showActions) {\n <div class=\"grid-cell data-cell actions-cell\"\n [style.width]=\"config?.actionsWidth || '150px'\"\n [style.min-width]=\"config?.actionsWidth || '150px'\"\n [style.max-width]=\"config?.actionsWidth || '150px'\">\n @if (actionsTemplate) {\n <ng-container *ngTemplateOutlet=\"actionsTemplate; context: { $implicit: row }\"></ng-container>\n }\n </div>\n }\n </div>\n }\n\n\n <!-- Empty State -->\n @if (gridData.length === 0 && !loading) {\n <div class=\"grid-empty\">\n <p>{{ config?.emptyMessage || 'No records found' }}</p>\n </div>\n }\n\n </div>\n\n <!-- Footer / Pagination -->\n @if (config?.showFooter !== false) {\n <div class=\"grid-footer\">\n <div class=\"pagination-info\">\n <span>\n Showing {{ pagination.page * pagination.pageSize + 1 }} to\n {{ Math.min((pagination.page + 1) * pagination.pageSize, pagination.totalRecords) }}\n of {{ pagination.totalRecords }} entries\n </span>\n </div>\n\n <div class=\"pagination-controls\">\n <!-- Page Size Selector -->\n @if (config?.pageSizeOptions; as pageSizeOptions) {\n <div class=\"page-size-selector\">\n <label>Rows per page:</label>\n <select [value]=\"pagination.pageSize\" (change)=\"onPageSizeChange(+$any($event.target).value)\"\n class=\"page-size-select\">\n @for (size of pageSizeOptions; track $index) {\n <option [value]=\"size\">\n {{ size }}\n </option>\n }\n </select>\n </div>\n }\n\n <!-- Page Navigation -->\n <div class=\"page-navigation\">\n <button class=\"page-btn\" [disabled]=\"pagination.page === 0\" (click)=\"onPageChange(0)\" title=\"First page\">\n \u27EA\n </button>\n <button class=\"page-btn\" [disabled]=\"pagination.page === 0\" (click)=\"onPageChange(pagination.page - 1)\"\n title=\"Previous page\">\n \u2039\n </button>\n\n @for (pageNum of getPageNumbers(); track pageNum) {\n @if (pageNum !== -1) {\n <button class=\"page-btn\" [class.active]=\"pageNum === pagination.page\" (click)=\"onPageChange(pageNum)\">\n {{ pageNum + 1 }}\n </button>\n }\n @if (pageNum === -1) {\n <span class=\"page-ellipsis\">...</span>\n }\n }\n\n <button class=\"page-btn\" [disabled]=\"pagination.page >= pagination.totalPages - 1\"\n (click)=\"onPageChange(pagination.page + 1)\" title=\"Next page\">\n \u203A\n </button>\n <button class=\"page-btn\" [disabled]=\"pagination.page >= pagination.totalPages - 1\"\n (click)=\"onPageChange(pagination.totalPages - 1)\" title=\"Last page\">\n \u27EB\n </button>\n </div>\n </div>\n\n <!-- Custom Footer Template -->\n @if (footerTemplate()) {\n <div class=\"grid-custom-footer\">\n <ng-container\n *ngTemplateOutlet=\"footerTemplate()!; context: { $implicit: gridData, total: pagination.totalRecords }\"></ng-container>\n </div>\n }\n </div>\n }\n\n </div>\n </div>\n\n <!-- Filter Modal -->\n @if (showFilterModal) {\n <div class=\"filter-modal-overlay\" (click)=\"closeFilterModal()\">\n <div class=\"filter-modal-content\" (click)=\"$event.stopPropagation()\">\n <div class=\"filter-modal-header\">\n <h3>Filter: {{ currentFilterColumn?.header }}</h3>\n <button class=\"close-btn\" (click)=\"closeFilterModal()\">\u00D7</button>\n </div>\n\n <div class=\"filter-modal-body\">\n <div class=\"filter-form\">\n <!-- Operator Selection - STRING -->\n @if (!currentFilterColumn?.filterType || currentFilterColumn?.filterType === 'text') {\n <div class=\"form-group\">\n <label>Operator:</label>\n <select [(ngModel)]=\"currentFilterOperator\" class=\"form-control\">\n <option value=\"contains\">Contains (\u0130\u00E7erir)</option>\n <option value=\"notContains\">Not Contains (\u0130\u00E7ermez)</option>\n <option value=\"equals\">Equals (E\u015Fittir)</option>\n <option value=\"notEquals\">Not Equals (E\u015Fit De\u011Fil)</option>\n <option value=\"startsWith\">Starts With (\u0130le Ba\u015Flar)</option>\n <option value=\"endsWith\">Ends With (\u0130le Biter)</option>\n <option value=\"isEmpty\">Is Empty (Bo\u015F)</option>\n <option value=\"isNotEmpty\">Is Not Empty (Bo\u015F De\u011Fil)</option>\n </select>\n </div>\n }\n\n <!-- Operator Selection - NUMBER -->\n @if (currentFilterColumn?.filterType === 'number') {\n <div class=\"form-group\">\n <label>Operator:</label>\n <select [(ngModel)]=\"currentFilterOperator\" class=\"form-control\">\n <option value=\"equals\">Equals (E\u015Fittir)</option>\n <option value=\"notEquals\">Not Equals (E\u015Fit De\u011Fil)</option>\n <option value=\"lt\">Less Than (K\u00FC\u00E7\u00FCkt\u00FCr)</option>\n <option value=\"lte\">Less Than or Equal (K\u00FC\u00E7\u00FCk E\u015Fit)</option>\n <option value=\"gt\">Greater Than (B\u00FCy\u00FCkt\u00FCr)</option>\n <option value=\"gte\">Greater Than or Equal (B\u00FCy\u00FCk E\u015Fit)</option>\n <option value=\"isEmpty\">Is Empty (Bo\u015F)</option>\n <option value=\"isNotEmpty\">Is Not Empty (Bo\u015F De\u011Fil)</option>\n </select>\n </div>\n }\n\n <!-- Operator Selection - DATE -->\n @if (currentFilterColumn?.filterType === 'date') {\n <div class=\"form-group\">\n <label>Operator:</label>\n <select [(ngModel)]=\"currentFilterOperator\" class=\"form-control\">\n <option value=\"equals\">On Date (Tarihte)</option>\n <option value=\"notEquals\">Not On Date (Tarihte De\u011Fil)</option>\n <option value=\"lt\">Before (\u00D6nce)</option>\n <option value=\"lte\">On or Before (Veya \u00D6nce)</option>\n <option value=\"gt\">After (Sonra)</option>\n <option value=\"gte\">On or After (Veya Sonra)</option>\n <option value=\"isEmpty\">Is Empty (Bo\u015F)</option>\n <option value=\"isNotEmpty\">Is Not Empty (Bo\u015F De\u011Fil)</option>\n </select>\n </div>\n }\n\n <!-- Value Input - TEXT -->\n @if ((!currentFilterColumn?.filterType || currentFilterColumn?.filterType === 'text') && currentFilterOperator\n !== 'isEmpty' && currentFilterOperator !== 'isNotEmpty') {\n <div class=\"form-group\">\n <label>Value:</label>\n <input type=\"text\" [(ngModel)]=\"filterValue\" class=\"form-control\" placeholder=\"Enter text...\"\n (keyup.enter)=\"applyFilterModal()\" />\n </div>\n }\n\n <!-- Value Input - NUMBER -->\n @if (currentFilterColumn?.filterType === 'number' && currentFilterOperator !== 'isEmpty' &&\n currentFilterOperator !== 'isNotEmpty') {\n <div class=\"form-group\">\n <label>Value:</label>\n <input type=\"number\" [(ngModel)]=\"filterValue\" class=\"form-control\" placeholder=\"Enter number...\"\n (keyup.enter)=\"applyFilterModal()\" />\n </div>\n }\n\n <!-- Value Input - DATE -->\n @if (currentFilterColumn?.filterType === 'date' && currentFilterOperator !== 'isEmpty' && currentFilterOperator\n !== 'isNotEmpty') {\n <div class=\"form-group\">\n <label>Value:</label>\n <input type=\"date\" [(ngModel)]=\"filterValue\" class=\"form-control\" (keyup.enter)=\"applyFilterModal()\" />\n </div>\n }\n\n\n @if (currentFilterOperator === 'isEmpty' || currentFilterOperator === 'isNotEmpty') {\n <div class=\"form-group\">\n <p\n style=\"padding: 12px; background: var(--grid-bg-secondary); border-radius: var(--grid-radius-sm); color: var(--grid-text-secondary); font-size: 0.875rem;\">\n \u2139\uFE0F Bu operat\u00F6r i\u00E7in de\u011Fer girmeye gerek yok.\n </p>\n </div>\n }\n\n </div>\n </div>\n\n <div class=\"filter-modal-footer\">\n <button class=\"btn-cancel\" (click)=\"closeFilterModal()\">Cancel</button>\n <button class=\"btn-clear\" (click)=\"clearFilterModal()\">Clear Filter</button>\n <button class=\"btn-apply\" (click)=\"applyFilterModal()\">Apply</button>\n </div>\n </div>\n </div>\n }", styles: ["@charset \"UTF-8\";:root{--grid-primary-color: #4096ff;--grid-primary-hover: #1677ff;--grid-primary-light: rgba(64, 150, 255, .1);--grid-bg-primary: #ffffff;--grid-bg-secondary: #fafafa;--grid-bg-hover: #f5f9ff;--grid-bg-selected: #e6f4ff;--grid-bg-selected-hover: #d9ecff;--grid-text-primary: #262626;--grid-text-secondary: #595959;--grid-text-tertiary: #8c8c8c;--grid-text-disabled: #bfbfbf;--grid-text-placeholder: #bfbfbf;--grid-border-color: #d9d9d9;--grid-border-light: #e8e8e8;--grid-border-dark: #e0e0e0;--grid-shadow-sm: 0 2px 8px rgba(0, 0, 0, .1);--grid-shadow-md: 0 4px 12px rgba(0, 0, 0, .15);--grid-shadow-lg: 0 6px 16px rgba(0, 0, 0, .12);--grid-shadow-color: rgba(0, 0, 0, .1);--grid-success-color: #52c41a;--grid-warning-color: #faad14;--grid-error-color: #ff4d4f;--grid-info-color: #1890ff;--grid-transition: all .3s ease;--grid-transition-fast: all .2s ease;--grid-transition-normal: all .3s ease;--grid-radius-sm: 4px;--grid-radius-md: 6px;--grid-radius-lg: 8px;--grid-header-bg: linear-gradient(180deg, #fafafa 0%, #f5f5f5 100%);--grid-footer-bg: #fafafa}[data-theme=dark],.dark-theme{--grid-primary-color: #1890ff;--grid-primary-hover: #40a9ff;--grid-primary-light: rgba(24, 144, 255, .2);--grid-bg-primary: #1a1a1a;--grid-bg-secondary: #0a0a0a;--grid-bg-hover: #2a2a2a;--grid-bg-selected: #111b26;--grid-bg-selected-hover: #0d1520;--grid-text-primary: #e8e8e8;--grid-text-secondary: #bfbfbf;--grid-text-tertiary: #8c8c8c;--grid-text-disabled: #595959;--grid-text-placeholder: #595959;--grid-border-color: #434343;--grid-border-light: #303030;--grid-border-dark: #4a4a4a;--grid-shadow-sm: 0 2px 8px rgba(0, 0, 0, .45);--grid-shadow-md: 0 4px 12px rgba(0, 0, 0, .55);--grid-shadow-lg: 0 6px 16px rgba(0, 0, 0, .65);--grid-shadow-color: rgba(0, 0, 0, .5);--grid-success-color: #49aa19;--grid-warning-color: #d89614;--grid-error-color: #d32029;--grid-info-color: #177ddc;--grid-header-bg: linear-gradient(180deg, #1f1f1f 0%, #1a1a1a 100%);--grid-footer-bg: #1f1f1f}*{transition:background-color .3s ease,color .3s ease,border-color .3s ease}body{background-color:var(--grid-bg-primary);color:var(--grid-text-primary);transition:background-color .3s ease,color .3s ease}.flexi-data-grid{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:var(--grid-text-primary);background:var(--grid-bg-primary);border-radius:var(--grid-radius-lg);box-shadow:var(--grid-shadow-sm);overflow:hidden}.flexi-data-grid.loading{pointer-events:none}.grid-loading-overlay{position:absolute;inset:0;background:var(--grid-bg-primary);opacity:.95;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:1000;gap:12px}.grid-loading-overlay .spinner{width:40px;height:40px;border:4px solid var(--grid-border-light);border-top:4px solid var(--grid-primary-color);border-radius:50%;animation:spin 1s cubic-bezier(.5,0,.5,1) infinite}.grid-loading-overlay:after{content:\"Loading...\";color:var(--grid-text-secondary);font-size:14px;font-weight:500}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.grid-container{display:flex;flex-direction:column;width:100%;min-height:400px;position:relative}.grid-toolbar{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:var(--grid-header-bg);border-bottom:1px solid var(--grid-border-dark);gap:16px;flex-wrap:wrap}.grid-toolbar .toolbar-left{display:flex;flex:0 0 auto;align-items:center;gap:12px;flex-wrap:wrap}.grid-toolbar .toolbar-center{display:flex;flex:1 1 auto;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap}.grid-toolbar .toolbar-right{display:flex;flex:0 0 auto;align-items:center;gap:4px;flex-wrap:wrap}.global-search{flex:1;min-width:200px;max-width:400px}.global-search .global-search-input{width:100%;padding:8px 12px;border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-md);font-size:14px;background:var(--grid-bg-primary);color:var(--grid-text-primary);transition:var(--grid-transition-fast)}.global-search .global-search-input:focus{outline:none;border-color:var(--grid-primary-color);box-shadow:0 0 0 2px var(--grid-primary-light)}.global-search .global-search-input::placeholder{color:var(--grid-text-placeholder)}.toolbar-btn{padding:8px 16px;border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-md);font-size:13px;font-weight:500;cursor:pointer;background:var(--grid-bg-primary);color:var(--grid-text-primary);transition:var(--grid-transition-fast);display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.toolbar-btn:hover{border-color:var(--grid-primary-color);color:var(--grid-primary-color);transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.toolbar-btn:active{transform:translateY(0)}.toolbar-btn.btn-add{background:var(--grid-success-color);color:#fff;border-color:var(--grid-success-color)}.toolbar-btn.btn-add:hover{opacity:.9;color:#fff}.toolbar-btn.btn-export:hover{background:var(--grid-primary-light)}.toolbar-btn.btn-icon-only{padding:10px;font-size:20px;line-height:1}.toolbar-btn.btn-icon-only:hover{background:var(--grid-primary-light)}.export-buttons{display:flex;gap:8px}.column-chooser{position:relative;padding:12px 16px;background:var(--grid-header-bg);border-bottom:1px solid var(--grid-border-dark)}.column-chooser .column-chooser-btn{padding:8px 16px;background:var(--grid-bg-primary);border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-md);cursor:pointer;font-size:13px;font-weight:500;color:var(--grid-text-primary);transition:var(--grid-transition-fast);display:inline-flex;align-items:center;gap:6px;box-shadow:var(--grid-shadow-sm)}.column-chooser .column-chooser-btn.btn-icon-only{padding:10px;font-size:20px;line-height:1}.column-chooser .column-chooser-btn:hover{border-color:var(--grid-primary-color);color:var(--grid-primary-color);box-shadow:0 2px 4px var(--grid-primary-light);transform:translateY(-1px)}.column-chooser .column-chooser-btn:active{transform:translateY(0);box-shadow:0 1px 2px #0000000d}.column-chooser .column-chooser-backdrop{position:fixed;inset:0;background:transparent;z-index:999}.column-chooser .column-chooser-panel{position:absolute;top:calc(100% + 4px);left:16px;margin-top:4px;background:var(--grid-bg-primary);border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-lg);box-shadow:var(--grid-shadow-lg);z-index:1000;min-width:280px;max-width:400px;animation:slideDown .2s ease}@keyframes slideDown{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.column-chooser .column-chooser-panel .column-chooser-header{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid var(--grid-border-light);font-weight:600;font-size:14px;color:var(--grid-text-primary);background:var(--grid-header-bg);border-radius:var(--grid-radius-lg) var(--grid-radius-lg) 0 0}.column-chooser .column-chooser-panel .column-chooser-header button{background:none;border:none;cursor:pointer;font-size:18px;padding:0;width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:var(--grid-radius-md);color:var(--grid-text-tertiary);transition:var(--grid-transition-fast)}.column-chooser .column-chooser-panel .column-chooser-header button:hover{background:var(--grid-bg-hover);color:var(--grid-text-primary)}.column-chooser .column-chooser-panel .column-chooser-header button:active{background:var(--grid-bg-selected)}.column-chooser .column-chooser-panel .column-list{max-height:400px;overflow-y:auto;padding:8px 0;display:grid;grid-template-columns:repeat(3,1fr);gap:4px}.column-chooser .column-chooser-panel .column-item{padding:10px 16px;transition:var(--grid-transition-fast);margin:0 8px;border-radius:var(--grid-radius-md)}.column-chooser .column-chooser-panel .column-item:hover{background:var(--grid-bg-hover)}.column-chooser .column-chooser-panel .column-item label{display:flex;align-items:center;gap:10px;cursor:pointer;margin:0;-webkit-user-select:none;user-select:none}.column-chooser .column-chooser-panel .column-item label input[type=checkbox]{cursor:pointer;width:16px;height:16px;accent-color:var(--grid-primary-color)}.column-chooser .column-chooser-panel .column-item label span{flex:1;font-size:13px;color:var(--grid-text-primary)}.grid-row{display:flex;width:100%;border-bottom:1px solid var(--grid-border-light);min-width:fit-content}.grid-row:last-child{border-bottom:none}.grid-cell{flex:1 1 0;min-width:0;padding:12px 16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:flex;align-items:center;box-sizing:border-box}.grid-cell[style*=width]{flex:0 0 auto}.grid-header{background:var(--grid-header-bg);border-bottom:2px solid var(--grid-border-dark);overflow-x:auto;overflow-y:hidden}.grid-header::-webkit-scrollbar{display:none}.grid-header{-ms-overflow-style:none;scrollbar-width:none}.grid-header .header-row{border-bottom:none}.grid-header .header-cell{font-weight:600;color:var(--grid-text-primary);cursor:default;-webkit-user-select:none;user-select:none;position:relative;transition:var(--grid-transition-fast)}.grid-header .header-cell.sortable{cursor:pointer}.grid-header .header-cell.sortable:hover{background-color:var(--grid-bg-hover)}.grid-header .header-cell.dragging{opacity:.4;background-color:var(--grid-primary-light);cursor:grabbing!important;transform:scale(.98);box-shadow:var(--grid-shadow-md)}.grid-header .header-cell.drop-target{border-left:4px solid var(--grid-primary-color);background-color:var(--grid-primary-light)}.grid-header .header-cell.drop-target:before{content:\"\";position:absolute;left:-4px;top:0;bottom:0;width:4px;background:var(--grid-primary-color);box-shadow:0 0 8px var(--grid-primary-light)}.grid-header .header-cell[draggable=true]{cursor:grab;transition:var(--grid-transition-fast)}.grid-header .header-cell[draggable=true]:hover{background-color:var(--grid-primary-light)}.grid-header .header-cell[draggable=true]:active{cursor:grabbing}.grid-header .header-cell.resizing{-webkit-user-select:none;user-select:none;cursor:col-resize;background-color:var(--grid-primary-light)}.grid-header .header-cell.resizing .resize-handle{opacity:1;background-color:var(--grid-primary-light)}.grid-header .header-cell.resizing .resize-handle:after{background-color:var(--grid-primary-color);box-shadow:0 0 8px var(--grid-primary-light);width:4px}.grid-header .header-cell .header-content{display:flex;align-items:center;justify-content:space-between;flex:1;gap:8px}.grid-header .header-cell .header-title{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.grid-header .header-cell .header-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.grid-header .header-cell .sort-icon{color:var(--grid-text-tertiary);font-size:14px;font-weight:700;transition:var(--grid-transition-fast);opacity:.7}.header-cell.sortable:hover .grid-header .header-cell .sort-icon{opacity:1;color:var(--grid-primary-color)}.grid-header .header-cell .resize-handle{position:absolute;right:-6px;top:0;bottom:0;width:16px;cursor:col-resize;-webkit-user-select:none;user-select:none;z-index:10;display:flex;align-items:center;justify-content:center;opacity:.7;transition:var(--grid-transition-fast)}.grid-header .header-cell .resize-handle:hover{opacity:1;background-color:var(--grid-primary-light)}.grid-header .header-cell .resize-handle:before{content:\"\\22ee\";color:var(--grid-text-secondary);font-size:18px;font-weight:700;line-height:1;text-shadow:0 1px 2px var(--grid-shadow-color)}.grid-header .header-cell .resize-handle:hover:before{color:var(--grid-primary-color)}.grid-header .header-cell .resize-handle:after{content:\"\";position:absolute;right:6px;top:10%;bottom:10%;width:3px;background-color:var(--grid-border-color);border-radius:var(--grid-radius-sm);transition:var(--grid-transition-fast)}.grid-header .header-cell .resize-handle:hover:after{background-color:var(--grid-primary-color);box-shadow:0 0 4px var(--grid-primary-light)}.grid-header .filter-row{background:var(--grid-bg-secondary);border-top:1px solid var(--grid-border-light)}.grid-header .filter-row .filter-cell{padding:6px 10px}.grid-header .filter-row .filter-input{width:100%;padding:8px 12px;border:1px solid var(--grid-border-light);border-radius:var(--grid-radius-sm);font-size:13px;transition:all .2s ease;background-color:var(--grid-bg-primary);color:var(--grid-text-primary)}.grid-header .filter-row .filter-input:hover{border-color:var(--grid-primary-color)}.grid-header .filter-row .filter-input:focus{outline:none;border-color:var(--grid-primary-color);box-shadow:0 0 0 3px #4f46e51a}.grid-header .filter-row .filter-input::placeholder{color:var(--grid-text-tertiary);font-size:12px;opacity:.7}.grid-body{flex:1;overflow-x:auto;overflow-y:auto;min-height:200px;background:var(--grid-bg-primary)}.grid-body .data-row{transition:var(--grid-transition-fast);border-left:3px solid transparent}.grid-body .data-row:hover{background-color:var(--grid-bg-hover);border-left-color:var(--grid-primary-light);box-shadow:inset 0 0 0 1px var(--grid-primary-light)}.grid-body .data-row.selectable{cursor:pointer}.grid-body .data-row.selectable:active{background-color:var(--grid-bg-selected)}.grid-body .data-row.selected{background-color:var(--grid-bg-selected);border-left-color:var(--grid-primary-color)}.grid-body .data-row.selected:hover{background-color:var(--grid-bg-selected-hover)}.grid-body .data-row .data-cell{color:var(--grid-text-primary);font-size:13px;transition:var(--grid-transition-fast)}.grid-body .data-row .data-cell[data-type=number]{justify-content:flex-end;text-align:right;font-variant-numeric:tabular-nums}.grid-body .data-row .data-cell[data-type=boolean]{justify-content:center}.grid-body .data-row .data-cell[data-type=date]{font-variant-numeric:tabular-nums}.grid-body .grid-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:300px;color:var(--grid-text-tertiary);font-size:14px;padding:40px 20px}.grid-body .grid-empty:before{content:\"\\1f4c2\";font-size:48px;margin-bottom:16px;opacity:.5}.grid-body .grid-empty p{margin:0;font-weight:500}.grid-footer{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--grid-header-bg);border-top:1px solid var(--grid-border-light);flex-wrap:wrap;gap:12px}.grid-footer .pagination-info{font-size:13px;color:var(--grid-text-secondary)}.grid-footer .pagination-controls{display:flex;align-items:center;gap:16px}.grid-footer .page-size-selector{display:flex;align-items:center;gap:8px;font-size:13px}.grid-footer .page-size-selector label{color:var(--grid-text-secondary);margin:0}.grid-footer .page-size-selector .page-size-select{padding:4px 8px;border:1px solid var(--grid-border-color);border-radius:var(--grid-radius-md);font-size:13px;cursor:pointer;transition:var(--grid-transition-normal);background-color:var(--grid-bg-primary);color:var(--grid-text-primary)}.grid-footer .page-size-selector .page-size-select:focus{outline:none;border-color:var(--grid-primary-color);box-shadow:0 0 0 2px var(--grid-primary-light)}.grid-footer .page-navigation{display:flex;gap:4px}.grid-footer .page-navigation .page-btn{min-width:32px;height:32px;padding:0 8px;border:1px solid var(--grid-border-color);background:var(--grid-bg-primary);color:var(--grid-text-secondary);font-size:13px;border-radius:var(--grid-radius-md);cursor:pointer;transition:var(--grid-transition-normal);display:flex;align-items:center;justify-content:center}.grid-footer .page-navigation .page-btn:hover:not(:disabled){border-color:var(--grid-primary-color);color:var(--grid-primary-color)}.grid-footer .page-navigation .page-btn.active{background:var(--grid-primary-color);color:#fff;border-color:var(--grid-primary-color);font-weight:600}.grid-footer .page-navigation .page-btn:disabled{cursor:not-allowed;opacity:.5}.grid-footer .page-navigation .page-ellipsis{display:flex;align-items:center;padding:0 4px;color:var(--grid-text-tertiary)}.grid-custom-footer{padding:16px;background:var(--grid-bg-secondary);border-top:2px solid var(--grid-border-color);color:var(--grid-text-primary)}@media(max-width:768px){.grid-footer{flex-direction:column;align-items:stretch}.grid-footer .pagination-info{text-align:center}.grid-footer .pagination-controls{flex-direction:column;align-items:stretch}.grid-footer .page-navigation{justify-content:center}}.filter-modal-overlay{position:fixed;inset:0;background:#0009;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:1000;animation:fadeIn .25s ease-out}.filter-modal-content{background:var(--grid-bg-primary);border-radius:var(--grid-radius-lg);box-shadow:0 20px 60px #00000059;width:90%;max-width:450px;max-height:90vh;overflow:hidden;animation:slideIn .3s cubic-bezier(.34,1.56,.64,1);border:1px solid var(--grid-border-light)}.filter-modal-header{padding:20px 24px;border-bottom:1px solid var(--grid-border-light);display:flex;align-items:center;justify-content:space-between;background:linear-gradient(135deg,var(--grid-primary-color) 0%,#6366f1 100%)}.filter-modal-header h3{margin:0;font-size:1.125rem;font-weight:600;color:#fff;display:flex;align-items:center;gap:8px}.filter-modal-header h3:before{content:\"\\1f50d\";font-size:1.2rem}.filter-modal-header .close-btn{background:#fff3;border:none;font-size:1.5rem;color:#fff;cursor:pointer;padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:var(--grid-radius-sm);transition:all .2s ease}.filter-modal-header .close-btn:hover{background:#ffffff4d;transform:rotate(90deg)}.filter-modal-body{padding:24px}.filter-modal-body .form-group{margin-bottom:20px}.filter-modal-body .form-group label{display:block;margin-bottom:8px;font-weight:600;color:var(--grid-text-primary);font-size:.875rem;text-transform:uppercase;letter-spacing:.5px}.filter-modal-body .form-group .form-control{width:100%;padding:12px 14px;border:2px solid var(--grid-border-light);border-radius:var(--grid-radius-sm);background:var(--grid-bg-primary);color:var(--grid-text-primary);font-size:.9375rem;transition:all .2s ease}.filter-modal-body .form-group .form-control:focus{outline:none;border-color:var(--grid-primary-color);box-shadow:0 0 0 3px #4f46e51f;background:var(--grid-bg-primary)}.filter-modal-body .form-group .form-control:hover:not(:focus){border-color:var(--grid-border-dark)}.filter-modal-body .form-group select.form-control{cursor:pointer;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%234f46e5' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 12px center;padding-right:36px;appearance:none}.filter-modal-body .form-group input.form-control::placeholder{color:var(--grid-text-tertiary);opacity:.6}.filter-modal-footer{padding:16px 20px;border-top:1px solid var(--grid-border-light);display:flex;gap:10px;justify-content:flex-end;background:var(--grid-bg-secondary);border-radius:0 0 var(--grid-radius-lg) var(--grid-radius-lg)}.filter-modal-footer button{padding:10px 20px;border-radius:var(--grid-radius-sm);font-weight:500;font-size:.875rem;cursor:pointer;transition:all .2s ease;border:none;min-width:80px}.filter-modal-footer button.btn-cancel{background:var(--grid-bg-primary);color:var(--grid-text-primary);border:1px solid var(--grid-border-light)}.filter-modal-footer button.btn-cancel:hover{background:var(--grid-bg-hover);border-color:var(--grid-border-dark)}.filter-modal-footer button.btn-clear{background:transparent;color:#ef4444;border:1px solid #ef4444}.filter-modal-footer button.btn-clear:hover{background:#ef44441a;border-color:#dc2626;color:#dc2626}.filter-modal-footer button.btn-apply{background:var(--grid-primary-color);color:#fff;border:1px solid var(--grid-primary-color)}.filter-modal-footer button.btn-apply:hover{background:#4338ca;transform:translateY(-1px);box-shadow:0 4px 12px #4f46e54d}.filter-modal-footer button.btn-apply:disabled{opacity:.5;cursor:not-allowed;transform:none}.filter-btn{background:transparent;border:none;cursor:pointer;padding:4px 6px;border-radius:var(--grid-radius-sm);font-size:.875rem;color:var(--grid-text-tertiary);transition:all .2s ease;opacity:.7;line-height:1;display:flex;align-items:center;justify-content:center}.filter-btn:hover{background:var(--grid-bg-hover);opacity:1;color:var(--grid-text-primary);transform:scale(1.1)}.filter-btn.active{color:var(--grid-primary-color);opacity:1;background:#4f46e51f}.filter-btn.active:hover{background:#4f46e52e}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideIn{0%{opacity:0;transform:translateY(-30px) scale(.9)}to{opacity:1;transform:translateY(0) scale(1)}}.grid-body::-webkit-scrollbar{width:8px;height:8px}.grid-body::-webkit-scrollbar-track{background:var(--grid-bg-secondary)}.grid-body::-webkit-scrollbar-thumb{background:var(--grid-border-dark);border-radius:var(--grid-radius-md)}.grid-body::-webkit-scrollbar-thumb:hover{background:var(--grid-text-tertiary)}.checkbox-cell{display:flex;align-items:center;justify-content:center;padding:8px!important;flex:0 0 auto!important}.checkbox-cell .grid-checkbox{position:relative;appearance:none;-webkit-appearance:none;-moz-appearance:none;cursor:pointer;width:20px;height:20px;border:2px solid var(--grid-border-dark);border-radius:var(--grid-radius-sm);background:var(--grid-bg-primary);transition:all .25s cubic-bezier(.4,0,.2,1);outline:none}.checkbox-cell .grid-checkbox:hover{border-color:var(--grid-primary-color);background:var(--grid-bg-hover);transform:scale(1.05);box-shadow:0 0 0 4px #4096ff14}.checkbox-cell .grid-checkbox:focus,.checkbox-cell .grid-checkbox:focus-visible{border-color:var(--grid-primary-color);box-shadow:0 0 0 4px #4096ff26}.checkbox-cell .grid-checkbox:checked{background:var(--grid-primary-color);border-color:var(--grid-primary-color)}.checkbox-cell .grid-checkbox:checked:before{content:\"\";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%) rotate(45deg) scale(1);width:5px;height:10px;border:solid white;border-width:0 2px 2px 0;opacity:1}.checkbox-cell .grid-checkbox:checked:hover{background:var(--grid-primary-hover, #1677ff);border-color:var(--grid-primary-hover, #1677ff)}.checkbox-cell .grid-checkbox:indeterminate{background:var(--grid-primary-color);border-color:var(--grid-primary-color)}.checkbox-cell .grid-checkbox:indeterminate:before{content:\"\";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:10px;height:2px;background:#fff;border-radius:1px}.checkbox-cell .grid-checkbox:disabled{cursor:not-allowed;opacity:.5;background:var(--grid-bg-secondary);border-color:var(--grid-border-light)}.checkbox-cell .grid-checkbox:disabled:hover{transform:none;box-shadow:none}.checkbox-cell .grid-checkbox:before{transition:all .2s cubic-bezier(.4,0,.2,1);opacity:0}.checkbox-cell .header-checkbox{width:20px;height:20px;border-width:2px}.checkbox-cell .header-checkbox:checked:before{width:5px;height:10px;border-width:0 2.5px 2.5px 0}.checkbox-cell .row-checkbox{width:18px;height:18px}.checkbox-cell .row-checkbox:checked:before{width:4px;height:9px;border-width:0 2px 2px 0}.actions-cell{display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 12px!important;flex:0 0 auto!important}.actions-cell button{padding:6px 12px;border:1px solid var(--grid-border-dark);background:var(--grid-bg-primary);color:var(--grid-text-primary);border-radius:var(--grid-radius-sm);cursor:pointer;font-size:13px;font-weight:500;transition:var(--grid-transition-fast);white-space:nowrap}.actions-cell button:hover{background:var(--grid-bg-hover);border-color:var(--grid-primary-color);color:var(--grid-primary-color);transform:translateY(-1px)}.actions-cell button:active{transform:translateY(0)}.actions-cell button:focus{outline:2px solid var(--grid-primary-color);outline-offset:2px}.actions-cell button.btn-edit{color:var(--grid-primary-color);border-color:var(--grid-primary-color)}.actions-cell button.btn-edit:hover{background:var(--grid-primary-color);color:#fff}.actions-cell button.btn-delete{color:#ef4444;border-color:#ef4444}.actions-cell button.btn-delete:hover{background:#ef4444;color:#fff}.actions-cell button.btn-view{color:#10b981;border-color:#10b981}.actions-cell button.btn-view:hover{background:#10b981;color:#fff}.actions-cell button:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.actions-cell .icon-btn{padding:6px;min-width:32px;display:flex;align-items:center;justify-content:center}\n"] }]
1680
+ }], ctorParameters: () => [{ type: GridDataService }, { type: ThemeService }], propDecorators: { config: [{
1681
+ type: Input
1682
+ }], adapter: [{
1683
+ type: Input
1684
+ }], autoLoad: [{
1685
+ type: Input
1686
+ }], lazy: [{
1687
+ type: Input
1688
+ }], data: [{
1689
+ type: Input
1690
+ }], totalRecords: [{
1691
+ type: Input
1692
+ }], gridHeader: [{
1693
+ type: ViewChild,
1694
+ args: ['gridHeader', { read: ElementRef }]
1695
+ }], gridBody: [{
1696
+ type: ViewChild,
1697
+ args: ['gridBody', { read: ElementRef }]
1698
+ }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], showThemeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showThemeSelector", required: false }] }], showColumnChooser: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnChooser", required: false }] }], showGlobalSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGlobalSearch", required: false }] }], showAddButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAddButton", required: false }] }], addButtonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "addButtonText", required: false }] }], addButtonUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "addButtonUrl", required: false }] }], showExport: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExport", required: false }] }], exportFileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportFileName", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], addClick: [{
1699
+ type: Output
1700
+ }], excelExport: [{
1701
+ type: Output
1702
+ }], pdfExport: [{
1703
+ type: Output
1704
+ }], onRefresh: [{
1705
+ type: Output
1706
+ }], columnComponents: [{
1707
+ type: ContentChildren,
1708
+ args: [GridColumnComponent]
1709
+ }], columnCommandTemplates: [{
1710
+ type: ContentChildren,
1711
+ args: [GridColumnCommandTemplateDirective]
1712
+ }], captionCommandTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => GridCaptionCommandTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], footerTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => GridFooterTemplateDirective), { ...{ read: TemplateRef }, isSignal: true }] }], dataLoad: [{
1713
+ type: Output
1714
+ }], rowSelect: [{
1715
+ type: Output
1716
+ }], rowUnselect: [{
1717
+ type: Output
1718
+ }], stateChange: [{
1719
+ type: Output
1720
+ }] } });
1721
+
1722
+ class GridCellTemplateDirective {
1723
+ templateRef;
1724
+ constructor(templateRef) {
1725
+ this.templateRef = templateRef;
1726
+ }
1727
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridCellTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
1728
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: GridCellTemplateDirective, isStandalone: true, selector: "[nlabsGridCellTemplate]", ngImport: i0 });
1729
+ }
1730
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridCellTemplateDirective, decorators: [{
1731
+ type: Directive,
1732
+ args: [{
1733
+ selector: '[nlabsGridCellTemplate]',
1734
+ standalone: true
1735
+ }]
1736
+ }], ctorParameters: () => [{ type: i0.TemplateRef }] });
1737
+
1738
+ class GridHeaderTemplateDirective {
1739
+ templateRef;
1740
+ constructor(templateRef) {
1741
+ this.templateRef = templateRef;
1742
+ }
1743
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridHeaderTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
1744
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: GridHeaderTemplateDirective, isStandalone: true, selector: "[nlabsGridHeaderTemplate]", ngImport: i0 });
1745
+ }
1746
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GridHeaderTemplateDirective, decorators: [{
1747
+ type: Directive,
1748
+ args: [{
1749
+ selector: '[nlabsGridHeaderTemplate]',
1750
+ standalone: true
1751
+ }]
1752
+ }], ctorParameters: () => [{ type: i0.TemplateRef }] });
1753
+
1754
+ /**
1755
+ * Grid Configuration Models
1756
+ */
1757
+
1758
+ /**
1759
+ * Mock data adapter for demo purposes
1760
+ * Simulates backend API with mock data
1761
+ */
1762
+ class MockDataAdapter {
1763
+ mockUsers = this.generateMockUsers(500);
1764
+ getData(request) {
1765
+ // Simulate network delay
1766
+ return of(this.processRequest(request)).pipe(delay(300));
1767
+ }
1768
+ buildQuery(request) {
1769
+ // Not used in mock adapter
1770
+ return '';
1771
+ }
1772
+ processRequest(request) {
1773
+ let data = [...this.mockUsers];
1774
+ // Apply filtering
1775
+ if (request.filter) {
1776
+ data = this.applyFilter(data, request.filter);
1777
+ }
1778
+ // Apply sorting
1779
+ if (request.orderBy) {
1780
+ data = this.applySort(data, request.orderBy);
1781
+ }
1782
+ const total = data.length;
1783
+ // Apply pagination
1784
+ const start = request.skip;
1785
+ const end = start + request.top;
1786
+ data = data.slice(start, end);
1787
+ return { data, total };
1788
+ }
1789
+ applyFilter(data, filter) {
1790
+ // Simple contains filter for demo
1791
+ const lowerFilter = filter.toLowerCase();
1792
+ return data.filter(user => user.name.toLowerCase().includes(lowerFilter) ||
1793
+ user.email.toLowerCase().includes(lowerFilter) ||
1794
+ user.city.toLowerCase().includes(lowerFilter));
1795
+ }
1796
+ applySort(data, orderBy) {
1797
+ const [field, order] = orderBy.split(' ');
1798
+ const multiplier = order === 'desc' ? -1 : 1;
1799
+ return data.sort((a, b) => {
1800
+ if (a[field] < b[field])
1801
+ return -1 * multiplier;
1802
+ if (a[field] > b[field])
1803
+ return 1 * multiplier;
1804
+ return 0;
1805
+ });
1806
+ }
1807
+ generateMockUsers(count) {
1808
+ const firstNames = ['John', 'Jane', 'Michael', 'Emily', 'David', 'Sarah', 'Chris', 'Anna', 'Tom', 'Lisa', 'Robert', 'Jennifer', 'William', 'Amanda', 'James', 'Jessica', 'Daniel', 'Ashley', 'Matthew', 'Brittany'];
1809
+ const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez', 'Hernandez', 'Lopez', 'Gonzalez', 'Wilson', 'Anderson', 'Thomas', 'Taylor', 'Moore', 'Jackson', 'Martin'];
1810
+ const cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia', 'San Antonio', 'San Diego', 'Dallas', 'San Jose', 'Austin', 'Jacksonville', 'Fort Worth', 'Columbus', 'San Francisco', 'Charlotte', 'Indianapolis', 'Seattle', 'Denver', 'Washington DC'];
1811
+ const countries = ['USA', 'Canada', 'UK', 'Germany', 'France', 'Spain', 'Italy', 'Australia', 'Japan', 'Brazil'];
1812
+ const departments = ['Engineering', 'Sales', 'Marketing', 'HR', 'Finance', 'Operations', 'IT', 'Legal', 'Customer Service', 'R&D'];
1813
+ const jobTitles = ['Senior Manager', 'Team Lead', 'Developer', 'Designer', 'Analyst', 'Consultant', 'Specialist', 'Coordinator', 'Director', 'VP'];
1814
+ const streets = ['Main St', 'Oak Ave', 'Pine Rd', 'Maple Dr', 'Cedar Ln', 'Elm St', 'Park Blvd', 'Lake View', 'Hill Crest', 'River Side'];
1815
+ const users = [];
1816
+ for (let i = 1; i <= count; i++) {
1817
+ const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
1818
+ const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
1819
+ const name = `${firstName} ${lastName}`;
1820
+ const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}${i}@example.com`;
1821
+ const age = Math.floor(Math.random() * 50) + 18;
1822
+ const salary = Math.floor(Math.random() * 150000) + 30000;
1823
+ const department = departments[Math.floor(Math.random() * departments.length)];
1824
+ const city = cities[Math.floor(Math.random() * cities.length)];
1825
+ const country = countries[Math.floor(Math.random() * countries.length)];
1826
+ const phone = `+1-${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 9000) + 1000}`;
1827
+ const street = streets[Math.floor(Math.random() * streets.length)];
1828
+ const address = `${Math.floor(Math.random() * 9999) + 1} ${street}`;
1829
+ const zipCode = String(Math.floor(Math.random() * 90000) + 10000);
1830
+ const jobTitle = jobTitles[Math.floor(Math.random() * jobTitles.length)];
1831
+ const managerFirstName = firstNames[Math.floor(Math.random() * firstNames.length)];
1832
+ const managerLastName = lastNames[Math.floor(Math.random() * lastNames.length)];
1833
+ const manager = `${managerFirstName} ${managerLastName}`;
1834
+ const active = Math.random() > 0.3;
1835
+ const startDate = new Date(2015 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1);
1836
+ const registeredDate = new Date(2020 + Math.floor(Math.random() * 5), Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1);
1837
+ const lastLogin = new Date(2024, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1);
1838
+ const notes = `Employee note #${i} - Performance review pending`;
1839
+ users.push({
1840
+ id: i,
1841
+ name,
1842
+ email,
1843
+ age,
1844
+ salary,
1845
+ department,
1846
+ city,
1847
+ country,
1848
+ phone,
1849
+ address,
1850
+ zipCode,
1851
+ jobTitle,
1852
+ manager,
1853
+ active,
1854
+ startDate,
1855
+ registeredDate,
1856
+ lastLogin,
1857
+ notes
1858
+ });
1859
+ }
1860
+ return users;
1861
+ }
1862
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: MockDataAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1863
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: MockDataAdapter, providedIn: 'root' });
1864
+ }
1865
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: MockDataAdapter, decorators: [{
1866
+ type: Injectable,
1867
+ args: [{
1868
+ providedIn: 'root'
1869
+ }]
1870
+ }] });
1871
+
1872
+ //Adapters
1873
+ //Styles
1874
+ // SCSS files cannot be exported from TypeScript entry points
1875
+
1876
+ /**
1877
+ * Generated bundle index. Do not edit.
1878
+ */
1879
+
1880
+ export { DataGridComponent, GridCaptionCommandTemplateDirective, GridCellTemplateDirective, GridColumnCommandTemplateDirective, GridColumnComponent, GridDataService, GridFooterTemplateDirective, GridHeaderTemplateDirective, MockDataAdapter, ODATA_BASE_URL, ODataAdapter, RestAdapter, ThemeSelectorComponent, ThemeService };
1881
+ //# sourceMappingURL=nlabtech-nlabs-grid.mjs.map