adminator-admin-dashboard 2.7.1 → 2.8.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/CHANGELOG.md +50 -0
- package/README.md +40 -12
- package/dist/main.js +1771 -2565
- package/dist/main.js.map +1 -1
- package/package.json +22 -34
- package/src/assets/scripts/app.js +3 -3
- package/src/assets/scripts/utils/theme.js +4 -2
- package/src/assets/scripts/vectorMaps/index.js +5 -5
- package/dist/55b07f26c86c8e3d3754.svg +0 -1
- package/dist/9fad440d8ee7a949a9a9.svg +0 -1
- package/dist/test.html +0 -91
- package/src/assets/scripts/app.ts +0 -757
- package/src/assets/scripts/components/Chart.ts +0 -1350
- package/src/assets/scripts/components/Sidebar.ts +0 -388
- package/src/assets/scripts/datatable/index.ts +0 -707
- package/src/assets/scripts/datepicker/index.ts +0 -699
- package/src/assets/scripts/ui/index.ts +0 -740
- package/src/assets/scripts/utils/date.ts +0 -363
- package/src/assets/scripts/utils/dom.ts +0 -513
- package/src/assets/scripts/utils/theme.ts +0 -313
- package/src/assets/scripts/vectorMaps/index.ts +0 -542
- package/src/test.html +0 -96
- package/src/types/index.ts +0 -236
- /package/dist/assets/{c1e38fd9e0e74ba58f7a2b77ef29fdd3.svg → fontawesome-webfont.svg} +0 -0
- /package/dist/assets/{f0fc8c798eac5636249c4ea287832422.svg → themify.svg} +0 -0
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DataTable Implementation with TypeScript
|
|
3
|
-
* Vanilla JavaScript DataTable with sorting, searching, and pagination
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ComponentInterface } from '../../types';
|
|
7
|
-
|
|
8
|
-
// Type definitions for DataTable
|
|
9
|
-
export interface DataTableOptions {
|
|
10
|
-
sortable?: boolean;
|
|
11
|
-
searchable?: boolean;
|
|
12
|
-
pagination?: boolean;
|
|
13
|
-
pageSize?: number;
|
|
14
|
-
responsive?: boolean;
|
|
15
|
-
striped?: boolean;
|
|
16
|
-
bordered?: boolean;
|
|
17
|
-
hover?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface DataTableColumn {
|
|
21
|
-
title: string;
|
|
22
|
-
data: string | number;
|
|
23
|
-
sortable?: boolean;
|
|
24
|
-
searchable?: boolean;
|
|
25
|
-
width?: string;
|
|
26
|
-
className?: string;
|
|
27
|
-
render?: (data: any, row: any[], index: number) => string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface DataTableData {
|
|
31
|
-
columns: DataTableColumn[];
|
|
32
|
-
rows: any[][];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface DataTableState {
|
|
36
|
-
currentPage: number;
|
|
37
|
-
sortColumn: number | null;
|
|
38
|
-
sortDirection: 'asc' | 'desc';
|
|
39
|
-
searchQuery: string;
|
|
40
|
-
filteredData: any[][];
|
|
41
|
-
totalPages: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type SortDirection = 'asc' | 'desc';
|
|
45
|
-
|
|
46
|
-
declare global {
|
|
47
|
-
interface HTMLTableElement {
|
|
48
|
-
dataTableInstance?: VanillaDataTable;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Enhanced DataTable implementation
|
|
53
|
-
export class VanillaDataTable implements ComponentInterface {
|
|
54
|
-
public name: string = 'VanillaDataTable';
|
|
55
|
-
public element: HTMLTableElement;
|
|
56
|
-
public options: DataTableOptions;
|
|
57
|
-
public isInitialized: boolean = false;
|
|
58
|
-
|
|
59
|
-
private originalData: any[][] = [];
|
|
60
|
-
private filteredData: any[][] = [];
|
|
61
|
-
private state: DataTableState;
|
|
62
|
-
private wrapper: HTMLElement | null = null;
|
|
63
|
-
private searchInput: HTMLInputElement | null = null;
|
|
64
|
-
private infoElement: HTMLElement | null = null;
|
|
65
|
-
private paginationElement: HTMLElement | null = null;
|
|
66
|
-
|
|
67
|
-
constructor(element: HTMLTableElement, options: DataTableOptions = {}) {
|
|
68
|
-
this.element = element;
|
|
69
|
-
this.options = {
|
|
70
|
-
sortable: true,
|
|
71
|
-
searchable: true,
|
|
72
|
-
pagination: true,
|
|
73
|
-
pageSize: 10,
|
|
74
|
-
responsive: true,
|
|
75
|
-
striped: true,
|
|
76
|
-
bordered: true,
|
|
77
|
-
hover: true,
|
|
78
|
-
...options,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
this.state = {
|
|
82
|
-
currentPage: 1,
|
|
83
|
-
sortColumn: null,
|
|
84
|
-
sortDirection: 'asc',
|
|
85
|
-
searchQuery: '',
|
|
86
|
-
filteredData: [],
|
|
87
|
-
totalPages: 0,
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
this.init();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public init(): void {
|
|
94
|
-
this.extractData();
|
|
95
|
-
this.createControls();
|
|
96
|
-
this.applyStyles();
|
|
97
|
-
this.bindEvents();
|
|
98
|
-
this.render();
|
|
99
|
-
this.isInitialized = true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public destroy(): void {
|
|
103
|
-
if (this.wrapper && this.wrapper.parentNode) {
|
|
104
|
-
this.wrapper.parentNode.replaceChild(this.element, this.wrapper);
|
|
105
|
-
}
|
|
106
|
-
this.isInitialized = false;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private extractData(): void {
|
|
110
|
-
const tbody = this.element.querySelector('tbody');
|
|
111
|
-
if (!tbody) return;
|
|
112
|
-
|
|
113
|
-
const rows = tbody.querySelectorAll('tr');
|
|
114
|
-
this.originalData = Array.from(rows).map(row => {
|
|
115
|
-
const cells = row.querySelectorAll('td');
|
|
116
|
-
return Array.from(cells).map(cell => cell.textContent?.trim() || '');
|
|
117
|
-
});
|
|
118
|
-
this.filteredData = [...this.originalData];
|
|
119
|
-
this.state.filteredData = this.filteredData;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private createControls(): void {
|
|
123
|
-
const wrapper = document.createElement('div');
|
|
124
|
-
wrapper.className = 'datatable-wrapper';
|
|
125
|
-
|
|
126
|
-
// Create top controls container
|
|
127
|
-
const topControls = document.createElement('div');
|
|
128
|
-
topControls.className = 'datatable-top-controls';
|
|
129
|
-
|
|
130
|
-
// Create search input
|
|
131
|
-
if (this.options.searchable) {
|
|
132
|
-
const searchWrapper = document.createElement('div');
|
|
133
|
-
searchWrapper.className = 'datatable-search';
|
|
134
|
-
|
|
135
|
-
const searchLabel = document.createElement('label');
|
|
136
|
-
searchLabel.textContent = 'Search: ';
|
|
137
|
-
|
|
138
|
-
this.searchInput = document.createElement('input');
|
|
139
|
-
this.searchInput.type = 'text';
|
|
140
|
-
this.searchInput.className = 'form-control';
|
|
141
|
-
this.searchInput.placeholder = 'Search...';
|
|
142
|
-
|
|
143
|
-
searchLabel.appendChild(this.searchInput);
|
|
144
|
-
searchWrapper.appendChild(searchLabel);
|
|
145
|
-
topControls.appendChild(searchWrapper);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Create info display
|
|
149
|
-
if (this.options.pagination) {
|
|
150
|
-
this.infoElement = document.createElement('div');
|
|
151
|
-
this.infoElement.className = 'datatable-info';
|
|
152
|
-
topControls.appendChild(this.infoElement);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
wrapper.appendChild(topControls);
|
|
156
|
-
|
|
157
|
-
// Wrap the table
|
|
158
|
-
if (this.element.parentNode) {
|
|
159
|
-
this.element.parentNode.insertBefore(wrapper, this.element);
|
|
160
|
-
}
|
|
161
|
-
wrapper.appendChild(this.element);
|
|
162
|
-
|
|
163
|
-
// Create pagination controls
|
|
164
|
-
if (this.options.pagination) {
|
|
165
|
-
this.paginationElement = document.createElement('div');
|
|
166
|
-
this.paginationElement.className = 'datatable-pagination';
|
|
167
|
-
wrapper.appendChild(this.paginationElement);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
this.wrapper = wrapper;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private applyStyles(): void {
|
|
174
|
-
// Apply Bootstrap-like styles
|
|
175
|
-
const classes = ['table'];
|
|
176
|
-
if (this.options.striped) classes.push('table-striped');
|
|
177
|
-
if (this.options.bordered) classes.push('table-bordered');
|
|
178
|
-
if (this.options.hover) classes.push('table-hover');
|
|
179
|
-
if (this.options.responsive) {
|
|
180
|
-
const responsiveWrapper = document.createElement('div');
|
|
181
|
-
responsiveWrapper.className = 'table-responsive';
|
|
182
|
-
if (this.element.parentNode) {
|
|
183
|
-
this.element.parentNode.insertBefore(responsiveWrapper, this.element);
|
|
184
|
-
responsiveWrapper.appendChild(this.element);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
this.element.className = classes.join(' ');
|
|
189
|
-
|
|
190
|
-
// Add custom styles
|
|
191
|
-
this.injectStyles();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private injectStyles(): void {
|
|
195
|
-
const styleId = 'datatable-styles';
|
|
196
|
-
if (document.getElementById(styleId)) return;
|
|
197
|
-
|
|
198
|
-
const style = document.createElement('style');
|
|
199
|
-
style.id = styleId;
|
|
200
|
-
style.textContent = `
|
|
201
|
-
.datatable-wrapper {
|
|
202
|
-
margin: 20px 0;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.datatable-top-controls {
|
|
206
|
-
display: flex;
|
|
207
|
-
justify-content: space-between;
|
|
208
|
-
align-items: center;
|
|
209
|
-
margin-bottom: 15px;
|
|
210
|
-
flex-wrap: wrap;
|
|
211
|
-
gap: 10px;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.datatable-search {
|
|
215
|
-
display: flex;
|
|
216
|
-
align-items: center;
|
|
217
|
-
gap: 8px;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.datatable-search label {
|
|
221
|
-
margin: 0;
|
|
222
|
-
font-weight: 500;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.datatable-search input {
|
|
226
|
-
width: 250px;
|
|
227
|
-
padding: 6px 12px;
|
|
228
|
-
border: 1px solid var(--c-border, #dee2e6);
|
|
229
|
-
border-radius: 4px;
|
|
230
|
-
font-size: 14px;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.datatable-info {
|
|
234
|
-
color: var(--c-text-muted, #6c757d);
|
|
235
|
-
font-size: 14px;
|
|
236
|
-
margin: 0;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
.datatable-pagination {
|
|
240
|
-
margin-top: 15px;
|
|
241
|
-
display: flex;
|
|
242
|
-
justify-content: center;
|
|
243
|
-
align-items: center;
|
|
244
|
-
gap: 4px;
|
|
245
|
-
flex-wrap: wrap;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.datatable-pagination button {
|
|
249
|
-
background: var(--c-bkg-card, #fff);
|
|
250
|
-
border: 1px solid var(--c-border, #dee2e6);
|
|
251
|
-
color: var(--c-text-base, #333);
|
|
252
|
-
padding: 8px 12px;
|
|
253
|
-
cursor: pointer;
|
|
254
|
-
border-radius: 4px;
|
|
255
|
-
font-size: 14px;
|
|
256
|
-
transition: all 0.2s ease;
|
|
257
|
-
min-width: 40px;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.datatable-pagination button:hover:not(:disabled) {
|
|
261
|
-
background: var(--c-primary, #007bff);
|
|
262
|
-
border-color: var(--c-primary, #007bff);
|
|
263
|
-
color: white;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
.datatable-pagination button.active {
|
|
267
|
-
background: var(--c-primary, #007bff);
|
|
268
|
-
border-color: var(--c-primary, #007bff);
|
|
269
|
-
color: white;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
.datatable-pagination button:disabled {
|
|
273
|
-
opacity: 0.6;
|
|
274
|
-
cursor: not-allowed;
|
|
275
|
-
background: var(--c-bkg-muted, #f8f9fa);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.datatable-sort {
|
|
279
|
-
cursor: pointer;
|
|
280
|
-
user-select: none;
|
|
281
|
-
position: relative;
|
|
282
|
-
padding-right: 20px !important;
|
|
283
|
-
transition: background-color 0.2s ease;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.datatable-sort:hover {
|
|
287
|
-
background: var(--c-bkg-hover, #f8f9fa);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
.datatable-sort::after {
|
|
291
|
-
content: '↕';
|
|
292
|
-
position: absolute;
|
|
293
|
-
right: 8px;
|
|
294
|
-
top: 50%;
|
|
295
|
-
transform: translateY(-50%);
|
|
296
|
-
opacity: 0.5;
|
|
297
|
-
font-size: 12px;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.datatable-sort.asc::after {
|
|
301
|
-
content: '↑';
|
|
302
|
-
opacity: 1;
|
|
303
|
-
color: var(--c-primary, #007bff);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
.datatable-sort.desc::after {
|
|
307
|
-
content: '↓';
|
|
308
|
-
opacity: 1;
|
|
309
|
-
color: var(--c-primary, #007bff);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
.datatable-no-results {
|
|
313
|
-
text-align: center;
|
|
314
|
-
color: var(--c-text-muted, #6c757d);
|
|
315
|
-
font-style: italic;
|
|
316
|
-
padding: 20px;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
@media (max-width: 768px) {
|
|
320
|
-
.datatable-top-controls {
|
|
321
|
-
flex-direction: column;
|
|
322
|
-
align-items: stretch;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
.datatable-search input {
|
|
326
|
-
width: 100%;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
.datatable-pagination {
|
|
330
|
-
justify-content: center;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
.datatable-pagination button {
|
|
334
|
-
padding: 6px 10px;
|
|
335
|
-
font-size: 13px;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
`;
|
|
339
|
-
document.head.appendChild(style);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
private bindEvents(): void {
|
|
343
|
-
// Search functionality
|
|
344
|
-
if (this.options.searchable && this.searchInput) {
|
|
345
|
-
this.searchInput.addEventListener('input', (e) => {
|
|
346
|
-
const target = e.target as HTMLInputElement;
|
|
347
|
-
this.search(target.value);
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Sorting functionality
|
|
352
|
-
if (this.options.sortable) {
|
|
353
|
-
const headers = this.element.querySelectorAll<HTMLTableCellElement>('thead th');
|
|
354
|
-
headers.forEach((header, index) => {
|
|
355
|
-
header.classList.add('datatable-sort');
|
|
356
|
-
header.addEventListener('click', () => {
|
|
357
|
-
this.sort(index);
|
|
358
|
-
});
|
|
359
|
-
header.setAttribute('tabindex', '0');
|
|
360
|
-
header.setAttribute('role', 'button');
|
|
361
|
-
header.setAttribute('aria-label', `Sort by ${header.textContent}`);
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
public search(query: string): void {
|
|
367
|
-
this.state.searchQuery = query;
|
|
368
|
-
|
|
369
|
-
if (!query.trim()) {
|
|
370
|
-
this.filteredData = [...this.originalData];
|
|
371
|
-
} else {
|
|
372
|
-
const searchTerm = query.toLowerCase().trim();
|
|
373
|
-
this.filteredData = this.originalData.filter(row =>
|
|
374
|
-
row.some(cell =>
|
|
375
|
-
cell.toString().toLowerCase().includes(searchTerm)
|
|
376
|
-
)
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
this.state.filteredData = this.filteredData;
|
|
381
|
-
this.state.currentPage = 1;
|
|
382
|
-
this.render();
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
public sort(columnIndex: number): void {
|
|
386
|
-
if (this.state.sortColumn === columnIndex) {
|
|
387
|
-
this.state.sortDirection = this.state.sortDirection === 'asc' ? 'desc' : 'asc';
|
|
388
|
-
} else {
|
|
389
|
-
this.state.sortColumn = columnIndex;
|
|
390
|
-
this.state.sortDirection = 'asc';
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
this.filteredData.sort((a, b) => {
|
|
394
|
-
const aVal = a[columnIndex];
|
|
395
|
-
const bVal = b[columnIndex];
|
|
396
|
-
|
|
397
|
-
// Try to parse as numbers
|
|
398
|
-
const aNum = parseFloat(aVal);
|
|
399
|
-
const bNum = parseFloat(bVal);
|
|
400
|
-
|
|
401
|
-
let comparison = 0;
|
|
402
|
-
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
403
|
-
comparison = aNum - bNum;
|
|
404
|
-
} else {
|
|
405
|
-
// Try to parse as dates
|
|
406
|
-
const aDate = new Date(aVal);
|
|
407
|
-
const bDate = new Date(bVal);
|
|
408
|
-
|
|
409
|
-
if (aDate.getTime() && bDate.getTime()) {
|
|
410
|
-
comparison = aDate.getTime() - bDate.getTime();
|
|
411
|
-
} else {
|
|
412
|
-
comparison = aVal.toString().localeCompare(bVal.toString());
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return this.state.sortDirection === 'asc' ? comparison : -comparison;
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
this.updateSortHeaders();
|
|
420
|
-
this.render();
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
private updateSortHeaders(): void {
|
|
424
|
-
const headers = this.element.querySelectorAll<HTMLTableCellElement>('thead th');
|
|
425
|
-
headers.forEach((header, index) => {
|
|
426
|
-
header.classList.remove('asc', 'desc');
|
|
427
|
-
if (index === this.state.sortColumn) {
|
|
428
|
-
header.classList.add(this.state.sortDirection);
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
public render(): void {
|
|
434
|
-
const tbody = this.element.querySelector('tbody');
|
|
435
|
-
if (!tbody) return;
|
|
436
|
-
|
|
437
|
-
const startIndex = (this.state.currentPage - 1) * this.options.pageSize!;
|
|
438
|
-
const endIndex = startIndex + this.options.pageSize!;
|
|
439
|
-
const pageData = this.filteredData.slice(startIndex, endIndex);
|
|
440
|
-
|
|
441
|
-
// Clear tbody
|
|
442
|
-
tbody.innerHTML = '';
|
|
443
|
-
|
|
444
|
-
if (pageData.length === 0) {
|
|
445
|
-
// Show no results message
|
|
446
|
-
const noResultsRow = document.createElement('tr');
|
|
447
|
-
const noResultsCell = document.createElement('td');
|
|
448
|
-
noResultsCell.colSpan = this.getColumnCount();
|
|
449
|
-
noResultsCell.className = 'datatable-no-results';
|
|
450
|
-
noResultsCell.textContent = this.state.searchQuery ?
|
|
451
|
-
'No matching records found' : 'No data available';
|
|
452
|
-
noResultsRow.appendChild(noResultsCell);
|
|
453
|
-
tbody.appendChild(noResultsRow);
|
|
454
|
-
} else {
|
|
455
|
-
// Add rows
|
|
456
|
-
pageData.forEach((rowData, rowIndex) => {
|
|
457
|
-
const row = document.createElement('tr');
|
|
458
|
-
rowData.forEach((cellData, colIndex) => {
|
|
459
|
-
const cell = document.createElement('td');
|
|
460
|
-
cell.textContent = cellData.toString();
|
|
461
|
-
row.appendChild(cell);
|
|
462
|
-
});
|
|
463
|
-
tbody.appendChild(row);
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Update pagination
|
|
468
|
-
if (this.options.pagination) {
|
|
469
|
-
this.updatePagination();
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Update info
|
|
473
|
-
this.updateInfo();
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
private getColumnCount(): number {
|
|
477
|
-
const headerRow = this.element.querySelector('thead tr');
|
|
478
|
-
return headerRow ? headerRow.querySelectorAll('th').length : 0;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
private updatePagination(): void {
|
|
482
|
-
if (!this.paginationElement) return;
|
|
483
|
-
|
|
484
|
-
this.state.totalPages = Math.ceil(this.filteredData.length / this.options.pageSize!);
|
|
485
|
-
this.paginationElement.innerHTML = '';
|
|
486
|
-
|
|
487
|
-
if (this.state.totalPages <= 1) return;
|
|
488
|
-
|
|
489
|
-
// Previous button
|
|
490
|
-
const prevBtn = this.createPaginationButton('Previous', () => {
|
|
491
|
-
if (this.state.currentPage > 1) {
|
|
492
|
-
this.state.currentPage--;
|
|
493
|
-
this.render();
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
prevBtn.disabled = this.state.currentPage === 1;
|
|
497
|
-
this.paginationElement.appendChild(prevBtn);
|
|
498
|
-
|
|
499
|
-
// Calculate page range to show
|
|
500
|
-
const maxButtons = 5;
|
|
501
|
-
let startPage = Math.max(1, this.state.currentPage - Math.floor(maxButtons / 2));
|
|
502
|
-
let endPage = Math.min(this.state.totalPages, startPage + maxButtons - 1);
|
|
503
|
-
|
|
504
|
-
// Adjust if we're at the end
|
|
505
|
-
if (endPage - startPage + 1 < maxButtons) {
|
|
506
|
-
startPage = Math.max(1, endPage - maxButtons + 1);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// First page if not in range
|
|
510
|
-
if (startPage > 1) {
|
|
511
|
-
const firstBtn = this.createPaginationButton('1', () => {
|
|
512
|
-
this.state.currentPage = 1;
|
|
513
|
-
this.render();
|
|
514
|
-
});
|
|
515
|
-
this.paginationElement.appendChild(firstBtn);
|
|
516
|
-
|
|
517
|
-
if (startPage > 2) {
|
|
518
|
-
const ellipsis = document.createElement('span');
|
|
519
|
-
ellipsis.textContent = '...';
|
|
520
|
-
ellipsis.className = 'pagination-ellipsis';
|
|
521
|
-
this.paginationElement.appendChild(ellipsis);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Page numbers
|
|
526
|
-
for (let i = startPage; i <= endPage; i++) {
|
|
527
|
-
const pageBtn = this.createPaginationButton(i.toString(), () => {
|
|
528
|
-
this.state.currentPage = i;
|
|
529
|
-
this.render();
|
|
530
|
-
});
|
|
531
|
-
pageBtn.classList.toggle('active', i === this.state.currentPage);
|
|
532
|
-
this.paginationElement.appendChild(pageBtn);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Last page if not in range
|
|
536
|
-
if (endPage < this.state.totalPages) {
|
|
537
|
-
if (endPage < this.state.totalPages - 1) {
|
|
538
|
-
const ellipsis = document.createElement('span');
|
|
539
|
-
ellipsis.textContent = '...';
|
|
540
|
-
ellipsis.className = 'pagination-ellipsis';
|
|
541
|
-
this.paginationElement.appendChild(ellipsis);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const lastBtn = this.createPaginationButton(this.state.totalPages.toString(), () => {
|
|
545
|
-
this.state.currentPage = this.state.totalPages;
|
|
546
|
-
this.render();
|
|
547
|
-
});
|
|
548
|
-
this.paginationElement.appendChild(lastBtn);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Next button
|
|
552
|
-
const nextBtn = this.createPaginationButton('Next', () => {
|
|
553
|
-
if (this.state.currentPage < this.state.totalPages) {
|
|
554
|
-
this.state.currentPage++;
|
|
555
|
-
this.render();
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
nextBtn.disabled = this.state.currentPage === this.state.totalPages;
|
|
559
|
-
this.paginationElement.appendChild(nextBtn);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
private createPaginationButton(text: string, onClick: () => void): HTMLButtonElement {
|
|
563
|
-
const button = document.createElement('button');
|
|
564
|
-
button.textContent = text;
|
|
565
|
-
button.addEventListener('click', onClick);
|
|
566
|
-
return button;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
private updateInfo(): void {
|
|
570
|
-
if (!this.infoElement) return;
|
|
571
|
-
|
|
572
|
-
const startIndex = (this.state.currentPage - 1) * this.options.pageSize! + 1;
|
|
573
|
-
const endIndex = Math.min(startIndex + this.options.pageSize! - 1, this.filteredData.length);
|
|
574
|
-
const total = this.filteredData.length;
|
|
575
|
-
const originalTotal = this.originalData.length;
|
|
576
|
-
|
|
577
|
-
if (total === 0) {
|
|
578
|
-
this.infoElement.textContent = 'No entries to show';
|
|
579
|
-
} else if (total === originalTotal) {
|
|
580
|
-
this.infoElement.textContent = `Showing ${startIndex} to ${endIndex} of ${total} entries`;
|
|
581
|
-
} else {
|
|
582
|
-
this.infoElement.textContent = `Showing ${startIndex} to ${endIndex} of ${total} entries (filtered from ${originalTotal} total entries)`;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Public API methods
|
|
587
|
-
public goToPage(page: number): void {
|
|
588
|
-
if (page >= 1 && page <= this.state.totalPages) {
|
|
589
|
-
this.state.currentPage = page;
|
|
590
|
-
this.render();
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
public setPageSize(size: number): void {
|
|
595
|
-
this.options.pageSize = size;
|
|
596
|
-
this.state.currentPage = 1;
|
|
597
|
-
this.render();
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
public getState(): Readonly<DataTableState> {
|
|
601
|
-
return { ...this.state };
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
public refresh(): void {
|
|
605
|
-
this.extractData();
|
|
606
|
-
this.state.currentPage = 1;
|
|
607
|
-
this.render();
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
public clear(): void {
|
|
611
|
-
this.originalData = [];
|
|
612
|
-
this.filteredData = [];
|
|
613
|
-
this.state.currentPage = 1;
|
|
614
|
-
this.render();
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
// DataTable Manager
|
|
619
|
-
export class DataTableManager {
|
|
620
|
-
private instances: Map<string, VanillaDataTable> = new Map();
|
|
621
|
-
|
|
622
|
-
public initialize(selector: string = '#dataTable', options: DataTableOptions = {}): VanillaDataTable | null {
|
|
623
|
-
const element = document.querySelector<HTMLTableElement>(selector);
|
|
624
|
-
if (!element) {
|
|
625
|
-
// Silently return null if element doesn't exist (normal for pages without tables)
|
|
626
|
-
return null;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// Clean up existing instance
|
|
630
|
-
if (element.dataTableInstance) {
|
|
631
|
-
element.dataTableInstance.destroy();
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// Create new instance
|
|
635
|
-
const dataTable = new VanillaDataTable(element, options);
|
|
636
|
-
element.dataTableInstance = dataTable;
|
|
637
|
-
|
|
638
|
-
// Store in manager
|
|
639
|
-
this.instances.set(selector, dataTable);
|
|
640
|
-
|
|
641
|
-
return dataTable;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
public getInstance(selector: string): VanillaDataTable | undefined {
|
|
645
|
-
return this.instances.get(selector);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
public destroyInstance(selector: string): void {
|
|
649
|
-
const instance = this.instances.get(selector);
|
|
650
|
-
if (instance) {
|
|
651
|
-
instance.destroy();
|
|
652
|
-
this.instances.delete(selector);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
public destroyAll(): void {
|
|
657
|
-
this.instances.forEach((instance, selector) => {
|
|
658
|
-
instance.destroy();
|
|
659
|
-
});
|
|
660
|
-
this.instances.clear();
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
// Create singleton manager
|
|
665
|
-
const dataTableManager = new DataTableManager();
|
|
666
|
-
|
|
667
|
-
// Initialize DataTable
|
|
668
|
-
const initializeDataTable = (): void => {
|
|
669
|
-
// Only initialize if the table exists
|
|
670
|
-
if (document.querySelector('#dataTable')) {
|
|
671
|
-
dataTableManager.initialize('#dataTable', {
|
|
672
|
-
sortable: true,
|
|
673
|
-
searchable: true,
|
|
674
|
-
pagination: true,
|
|
675
|
-
pageSize: 10,
|
|
676
|
-
responsive: true,
|
|
677
|
-
striped: true,
|
|
678
|
-
bordered: true,
|
|
679
|
-
hover: true,
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
};
|
|
683
|
-
|
|
684
|
-
// Initialize on load
|
|
685
|
-
if (document.readyState === 'loading') {
|
|
686
|
-
document.addEventListener('DOMContentLoaded', initializeDataTable);
|
|
687
|
-
} else {
|
|
688
|
-
initializeDataTable();
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Reinitialize on theme change
|
|
692
|
-
window.addEventListener('adminator:themeChanged', () => {
|
|
693
|
-
setTimeout(initializeDataTable, 100);
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
// Cleanup on page unload
|
|
697
|
-
window.addEventListener('beforeunload', () => {
|
|
698
|
-
dataTableManager.destroyAll();
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
// Export default for compatibility
|
|
702
|
-
export default {
|
|
703
|
-
init: initializeDataTable,
|
|
704
|
-
manager: dataTableManager,
|
|
705
|
-
VanillaDataTable,
|
|
706
|
-
DataTableManager,
|
|
707
|
-
};
|