@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.
- package/README.md +638 -0
- package/fesm2022/nlabtech-nlabs-grid.mjs +1881 -0
- package/fesm2022/nlabtech-nlabs-grid.mjs.map +1 -0
- package/package.json +23 -0
- package/types/nlabtech-nlabs-grid.d.ts +444 -0
|
@@ -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, '&')
|
|
1409
|
+
.replace(/</g, '<')
|
|
1410
|
+
.replace(/>/g, '>')
|
|
1411
|
+
.replace(/"/g, '"')
|
|
1412
|
+
.replace(/'/g, ''');
|
|
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
|