adminator-admin-dashboard 2.7.0 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,757 @@
1
+ /**
2
+ * Modern Adminator Application with TypeScript
3
+ * Main application entry point with enhanced mobile support and type safety
4
+ */
5
+
6
+ import { DOM } from './utils/dom';
7
+ import { ThemeManager } from './utils/theme';
8
+ import { Sidebar } from './components/Sidebar';
9
+ import { ChartComponent } from './components/Chart';
10
+ import UIComponents from './ui';
11
+ import DataTable from './datatable';
12
+ import DatePicker from './datepicker';
13
+ import VectorMaps from './vectorMaps';
14
+ import type { ComponentInterface } from '../../types';
15
+
16
+ // Import styles
17
+ import '../styles/index.scss';
18
+
19
+ // Import other modules that don't need immediate modernization
20
+ import './fullcalendar';
21
+ import './masonry';
22
+ import './popover';
23
+ import './scrollbar';
24
+ import './search';
25
+ import './skycons';
26
+ import './chat';
27
+ import './email';
28
+ import './googleMaps';
29
+
30
+ // Type definitions for the application
31
+ export interface AdminatorAppOptions {
32
+ autoInit?: boolean;
33
+ theme?: 'light' | 'dark' | 'auto';
34
+ mobile?: {
35
+ enhanced?: boolean;
36
+ fullWidthSearch?: boolean;
37
+ disableDropdowns?: boolean;
38
+ };
39
+ debug?: boolean;
40
+ }
41
+
42
+ export interface AdminatorAppState {
43
+ isInitialized: boolean;
44
+ isMobile: boolean;
45
+ currentTheme: 'light' | 'dark' | 'auto';
46
+ components: Map<string, ComponentInterface>;
47
+ }
48
+
49
+ export interface AdminatorAppEvents {
50
+ ready: CustomEvent<{ app: AdminatorApp }>;
51
+ themeChanged: CustomEvent<{ theme: string; previousTheme: string }>;
52
+ mobileStateChanged: CustomEvent<{ isMobile: boolean }>;
53
+ componentAdded: CustomEvent<{ name: string; component: ComponentInterface }>;
54
+ componentRemoved: CustomEvent<{ name: string }>;
55
+ }
56
+
57
+ declare global {
58
+ interface Window {
59
+ AdminatorApp?: AdminatorApp;
60
+ }
61
+ }
62
+
63
+ export class AdminatorApp {
64
+ public options: AdminatorAppOptions;
65
+ public state: AdminatorAppState;
66
+
67
+ private resizeTimeout: number | null = null;
68
+ private eventHandlers: Map<string, EventListener> = new Map();
69
+ private themeManager: typeof ThemeManager;
70
+
71
+ constructor(options: AdminatorAppOptions = {}) {
72
+ this.options = {
73
+ autoInit: true,
74
+ theme: 'auto',
75
+ mobile: {
76
+ enhanced: true,
77
+ fullWidthSearch: true,
78
+ disableDropdowns: false,
79
+ },
80
+ debug: false,
81
+ ...options,
82
+ };
83
+
84
+ this.themeManager = ThemeManager;
85
+
86
+ this.state = {
87
+ isInitialized: false,
88
+ isMobile: this.checkMobileState(),
89
+ currentTheme: 'light',
90
+ components: new Map(),
91
+ };
92
+
93
+ if (this.options.autoInit) {
94
+ // Initialize when DOM is ready
95
+ DOM.ready(() => {
96
+ this.init();
97
+ });
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Initialize the application
103
+ */
104
+ public init(): void {
105
+ if (this.state.isInitialized) return;
106
+
107
+ this.log('Initializing Adminator App...');
108
+
109
+ try {
110
+ // Initialize core components
111
+ this.initSidebar();
112
+ this.initCharts();
113
+ this.initDataTables();
114
+ this.initDatePickers();
115
+ this.initUIComponents();
116
+ this.initVectorMaps();
117
+ this.initTheme();
118
+ this.initMobileEnhancements();
119
+
120
+ // Setup global event listeners
121
+ this.setupGlobalEvents();
122
+
123
+ this.state.isInitialized = true;
124
+ this.log('Adminator App initialized successfully');
125
+
126
+ // Dispatch custom event for other scripts
127
+ this.dispatchEvent('ready', { app: this });
128
+
129
+ } catch (error) {
130
+ console.error('Error initializing Adminator App:', error);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Initialize Sidebar component
136
+ */
137
+ private initSidebar(): void {
138
+ if (DOM.exists('.sidebar')) {
139
+ const sidebar = new Sidebar();
140
+ this.addComponent('sidebar', sidebar);
141
+ this.log('Sidebar component initialized');
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Initialize Chart components
147
+ */
148
+ private initCharts(): void {
149
+ // Check if we have any chart elements
150
+ const hasCharts = DOM.exists('#sparklinedash') ||
151
+ DOM.exists('.sparkline') ||
152
+ DOM.exists('.sparkbar') ||
153
+ DOM.exists('.sparktri') ||
154
+ DOM.exists('.sparkdisc') ||
155
+ DOM.exists('.sparkbull') ||
156
+ DOM.exists('.sparkbox') ||
157
+ DOM.exists('.easy-pie-chart') ||
158
+ DOM.exists('#line-chart') ||
159
+ DOM.exists('#area-chart') ||
160
+ DOM.exists('#scatter-chart') ||
161
+ DOM.exists('#bar-chart');
162
+
163
+ if (hasCharts) {
164
+ const charts = new ChartComponent();
165
+ this.addComponent('charts', charts);
166
+ this.log('Chart components initialized');
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Initialize DataTables
172
+ */
173
+ private initDataTables(): void {
174
+ const dataTableElement = DOM.select('#dataTable');
175
+ if (dataTableElement) {
176
+ DataTable.init();
177
+ this.log('DataTable initialized');
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Initialize Date Pickers
183
+ */
184
+ private initDatePickers(): void {
185
+ const startDatePickers = DOM.selectAll('.start-date');
186
+ const endDatePickers = DOM.selectAll('.end-date');
187
+
188
+ if (startDatePickers.length > 0 || endDatePickers.length > 0) {
189
+ DatePicker.init();
190
+ this.log('Date pickers initialized');
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Initialize UI Components
196
+ */
197
+ private initUIComponents(): void {
198
+ UIComponents.init();
199
+ this.log('UI components initialized');
200
+ }
201
+
202
+ /**
203
+ * Initialize Vector Maps
204
+ */
205
+ private initVectorMaps(): void {
206
+ if (DOM.exists('#world-map-marker')) {
207
+ VectorMaps.init();
208
+ this.log('Vector maps initialized');
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Initialize theme system with toggle
214
+ */
215
+ private initTheme(): void {
216
+ this.log('Initializing theme system...');
217
+
218
+ // Initialize theme system first
219
+ this.themeManager.init();
220
+ this.state.currentTheme = this.themeManager.current();
221
+
222
+ // Inject theme toggle if missing
223
+ setTimeout(() => {
224
+ this.injectThemeToggle();
225
+ }, 100);
226
+ }
227
+
228
+ /**
229
+ * Inject theme toggle button
230
+ */
231
+ private injectThemeToggle(): void {
232
+ const navRight = DOM.select('.nav-right');
233
+
234
+ if (navRight && !DOM.exists('#theme-toggle')) {
235
+ const li = document.createElement('li');
236
+ li.className = 'theme-toggle d-flex ai-c';
237
+ li.innerHTML = `
238
+ <div class="form-check form-switch d-flex ai-c" style="margin: 0; padding: 0;">
239
+ <label class="form-check-label me-2 text-nowrap c-grey-700" for="theme-toggle" style="font-size: 12px; margin-right: 8px;">
240
+ <i class="ti-sun" style="margin-right: 4px;"></i><span class="theme-label">Light</span>
241
+ </label>
242
+ <input class="form-check-input" type="checkbox" id="theme-toggle" style="margin: 0;">
243
+ <label class="form-check-label ms-2 text-nowrap c-grey-700" for="theme-toggle" style="font-size: 12px; margin-left: 8px;">
244
+ <span class="theme-label">Dark</span><i class="ti-moon" style="margin-left: 4px;"></i>
245
+ </label>
246
+ </div>
247
+ `;
248
+
249
+ // Insert before user dropdown (last item)
250
+ const lastItem = navRight.querySelector('li:last-child');
251
+ if (lastItem && lastItem.parentNode === navRight) {
252
+ navRight.insertBefore(li, lastItem);
253
+ } else {
254
+ navRight.appendChild(li);
255
+ }
256
+
257
+ this.setupThemeToggle();
258
+ this.log('Theme toggle injected');
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Setup theme toggle functionality
264
+ */
265
+ private setupThemeToggle(): void {
266
+ const toggle = DOM.select('#theme-toggle') as HTMLInputElement;
267
+ if (!toggle) return;
268
+
269
+ // Set initial state
270
+ toggle.checked = this.state.currentTheme === 'dark';
271
+
272
+ // Add change handler
273
+ const changeHandler = (): void => {
274
+ const newTheme = toggle.checked ? 'dark' : 'light';
275
+ const previousTheme = this.state.currentTheme;
276
+
277
+ this.themeManager.apply(newTheme);
278
+ this.state.currentTheme = newTheme;
279
+
280
+ this.dispatchEvent('themeChanged', { theme: newTheme, previousTheme });
281
+ };
282
+
283
+ DOM.on(toggle, 'change', changeHandler);
284
+ this.eventHandlers.set('theme-toggle', changeHandler);
285
+
286
+ // Listen for theme changes from other sources
287
+ const themeChangeHandler = (event: CustomEvent): void => {
288
+ const newTheme = event.detail.theme;
289
+ toggle.checked = newTheme === 'dark';
290
+ this.state.currentTheme = newTheme;
291
+
292
+ // Update charts when theme changes
293
+ const charts = this.getComponent('charts') as ChartComponent;
294
+ if (charts && typeof charts.redrawCharts === 'function') {
295
+ charts.redrawCharts();
296
+ }
297
+ };
298
+
299
+ window.addEventListener('adminator:themeChanged', themeChangeHandler as EventListener);
300
+ this.eventHandlers.set('theme-change', themeChangeHandler as EventListener);
301
+ }
302
+
303
+ /**
304
+ * Initialize mobile-specific enhancements
305
+ */
306
+ private initMobileEnhancements(): void {
307
+ if (!this.options.mobile?.enhanced) return;
308
+
309
+ this.log('Initializing mobile enhancements...');
310
+ this.enhanceMobileDropdowns();
311
+ this.enhanceMobileSearch();
312
+
313
+ // Prevent horizontal scroll on mobile
314
+ if (this.state.isMobile) {
315
+ document.body.style.overflowX = 'hidden';
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Setup global event listeners
321
+ */
322
+ private setupGlobalEvents(): void {
323
+ // Global click handler
324
+ const globalClickHandler = (event: Event): void => {
325
+ this.handleGlobalClick(event);
326
+ };
327
+ DOM.on(document, 'click', globalClickHandler);
328
+ this.eventHandlers.set('global-click', globalClickHandler);
329
+
330
+ // Window resize handler with debouncing
331
+ const resizeHandler = (): void => {
332
+ if (this.resizeTimeout) {
333
+ clearTimeout(this.resizeTimeout);
334
+ }
335
+ this.resizeTimeout = window.setTimeout(() => {
336
+ this.handleResize();
337
+ }, 250);
338
+ };
339
+ DOM.on(window, 'resize', resizeHandler);
340
+ this.eventHandlers.set('resize', resizeHandler);
341
+
342
+ this.log('Global event listeners set up');
343
+ }
344
+
345
+ /**
346
+ * Handle window resize events
347
+ */
348
+ private handleResize(): void {
349
+ const wasMobile = this.state.isMobile;
350
+ this.state.isMobile = this.checkMobileState();
351
+
352
+ if (wasMobile !== this.state.isMobile) {
353
+ this.dispatchEvent('mobileStateChanged', { isMobile: this.state.isMobile });
354
+ }
355
+
356
+ this.log('Window resized, updating mobile features');
357
+
358
+ // Close all mobile-specific overlays when switching to desktop
359
+ if (!this.state.isMobile) {
360
+ document.body.style.overflow = '';
361
+ document.body.style.overflowX = '';
362
+
363
+ // Close dropdowns
364
+ const dropdowns = DOM.selectAll('.nav-right .dropdown');
365
+ dropdowns.forEach(dropdown => {
366
+ dropdown.classList.remove('show');
367
+ const menu = dropdown.querySelector('.dropdown-menu');
368
+ if (menu) menu.classList.remove('show');
369
+ });
370
+
371
+ // Close search
372
+ this.closeSearch();
373
+ } else {
374
+ // Re-enable mobile overflow protection
375
+ document.body.style.overflowX = 'hidden';
376
+ }
377
+
378
+ // Re-apply mobile enhancements
379
+ if (this.options.mobile?.enhanced) {
380
+ this.enhanceMobileDropdowns();
381
+ this.enhanceMobileSearch();
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Handle global click events
387
+ */
388
+ private handleGlobalClick(event: Event): void {
389
+ const target = event.target as HTMLElement;
390
+
391
+ // Close mobile dropdowns when clicking outside
392
+ if (!target.closest('.dropdown')) {
393
+ const dropdowns = DOM.selectAll('.nav-right .dropdown');
394
+ dropdowns.forEach(dropdown => {
395
+ dropdown.classList.remove('show');
396
+ const menu = dropdown.querySelector('.dropdown-menu');
397
+ if (menu) menu.classList.remove('show');
398
+ });
399
+ document.body.style.overflow = '';
400
+ }
401
+
402
+ // Close search when clicking outside
403
+ if (!target.closest('.search-box') && !target.closest('.search-input')) {
404
+ this.closeSearch();
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Check if we're on a mobile device
410
+ */
411
+ private checkMobileState(): boolean {
412
+ return window.innerWidth <= 768;
413
+ }
414
+
415
+ /**
416
+ * Enhanced mobile dropdown handling
417
+ */
418
+ private enhanceMobileDropdowns(): void {
419
+ if (!this.state.isMobile || this.options.mobile?.disableDropdowns) return;
420
+
421
+ const dropdowns = DOM.selectAll('.nav-right .dropdown');
422
+
423
+ dropdowns.forEach(dropdown => {
424
+ const toggle = dropdown.querySelector('.dropdown-toggle') as HTMLElement;
425
+ const menu = dropdown.querySelector('.dropdown-menu') as HTMLElement;
426
+
427
+ if (toggle && menu) {
428
+ // Remove existing listeners to prevent duplicates
429
+ const newToggle = toggle.cloneNode(true) as HTMLElement;
430
+ toggle.replaceWith(newToggle);
431
+
432
+ // Add click functionality for mobile dropdowns
433
+ DOM.on(newToggle, 'click', (e: Event) => {
434
+ e.preventDefault();
435
+ e.stopPropagation();
436
+
437
+ // Close search if open
438
+ this.closeSearch();
439
+
440
+ // Close other dropdowns first
441
+ dropdowns.forEach(otherDropdown => {
442
+ if (otherDropdown !== dropdown) {
443
+ otherDropdown.classList.remove('show');
444
+ const otherMenu = otherDropdown.querySelector('.dropdown-menu');
445
+ if (otherMenu) otherMenu.classList.remove('show');
446
+ }
447
+ });
448
+
449
+ // Toggle current dropdown
450
+ const isOpen = dropdown.classList.contains('show');
451
+ if (isOpen) {
452
+ dropdown.classList.remove('show');
453
+ menu.classList.remove('show');
454
+ document.body.style.overflow = '';
455
+ document.body.classList.remove('mobile-menu-open');
456
+ } else {
457
+ dropdown.classList.add('show');
458
+ menu.classList.add('show');
459
+ document.body.style.overflow = 'hidden';
460
+ document.body.classList.add('mobile-menu-open');
461
+ }
462
+ });
463
+
464
+ // Enhanced mobile close button functionality
465
+ DOM.on(menu, 'click', (e: Event) => {
466
+ const rect = menu.getBoundingClientRect();
467
+ const clickY = (e as MouseEvent).clientY - rect.top;
468
+
469
+ // If clicked in top 50px (close button area)
470
+ if (clickY <= 50) {
471
+ dropdown.classList.remove('show');
472
+ menu.classList.remove('show');
473
+ document.body.style.overflow = '';
474
+ document.body.classList.remove('mobile-menu-open');
475
+ e.preventDefault();
476
+ e.stopPropagation();
477
+ }
478
+ });
479
+ }
480
+ });
481
+
482
+ // Close dropdowns on escape key
483
+ const escapeHandler = (e: Event): void => {
484
+ const keyEvent = e as KeyboardEvent;
485
+ if (keyEvent.key === 'Escape') {
486
+ dropdowns.forEach(dropdown => {
487
+ dropdown.classList.remove('show');
488
+ const menu = dropdown.querySelector('.dropdown-menu');
489
+ if (menu) menu.classList.remove('show');
490
+ });
491
+ document.body.style.overflow = '';
492
+ document.body.classList.remove('mobile-menu-open');
493
+ }
494
+ };
495
+
496
+ DOM.on(document, 'keydown', escapeHandler);
497
+ }
498
+
499
+ /**
500
+ * Enhanced mobile search handling
501
+ */
502
+ private enhanceMobileSearch(): void {
503
+ if (!this.options.mobile?.fullWidthSearch) return;
504
+
505
+ const searchBox = DOM.select('.search-box') as HTMLElement;
506
+ const searchInput = DOM.select('.search-input') as HTMLElement;
507
+
508
+ if (searchBox && searchInput) {
509
+ const searchToggle = searchBox.querySelector('a') as HTMLAnchorElement;
510
+ const searchField = searchInput.querySelector('input') as HTMLInputElement;
511
+
512
+ if (searchToggle && searchField) {
513
+ // Remove existing listeners to prevent duplication
514
+ const newSearchToggle = searchToggle.cloneNode(true) as HTMLAnchorElement;
515
+ searchToggle.replaceWith(newSearchToggle);
516
+
517
+ DOM.on(newSearchToggle, 'click', (e: Event) => {
518
+ e.preventDefault();
519
+ e.stopPropagation();
520
+
521
+ // Close any open dropdowns first
522
+ const dropdowns = DOM.selectAll('.nav-right .dropdown');
523
+ dropdowns.forEach(dropdown => {
524
+ dropdown.classList.remove('show');
525
+ const menu = dropdown.querySelector('.dropdown-menu');
526
+ if (menu) menu.classList.remove('show');
527
+ });
528
+
529
+ // Toggle search state
530
+ const isActive = searchInput.classList.contains('active');
531
+ const searchIcon = newSearchToggle.querySelector('i') as HTMLElement;
532
+
533
+ if (isActive) {
534
+ this.closeSearch();
535
+ } else {
536
+ this.openSearch(searchField, searchIcon);
537
+ }
538
+ });
539
+
540
+ // Handle search input
541
+ DOM.on(searchField, 'keypress', (e: Event) => {
542
+ const keyEvent = e as KeyboardEvent;
543
+ if (keyEvent.key === 'Enter') {
544
+ keyEvent.preventDefault();
545
+ const query = searchField.value.trim();
546
+ if (query) {
547
+ this.handleSearch(query);
548
+ }
549
+ }
550
+ });
551
+ }
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Open search interface
557
+ */
558
+ private openSearch(searchField: HTMLInputElement, searchIcon: HTMLElement): void {
559
+ const searchInput = DOM.select('.search-input') as HTMLElement;
560
+
561
+ searchInput.classList.add('active');
562
+ document.body.classList.add('search-open');
563
+
564
+ // Change icon to close
565
+ if (searchIcon) {
566
+ searchIcon.className = 'ti-close';
567
+ }
568
+
569
+ // Focus the input after a short delay
570
+ setTimeout(() => {
571
+ searchField.focus();
572
+ }, 100);
573
+ }
574
+
575
+ /**
576
+ * Close search interface
577
+ */
578
+ private closeSearch(): void {
579
+ const searchBox = DOM.select('.search-box') as HTMLElement;
580
+ const searchInput = DOM.select('.search-input') as HTMLElement;
581
+
582
+ if (searchBox && searchInput) {
583
+ searchInput.classList.remove('active');
584
+ document.body.classList.remove('search-open');
585
+ document.body.classList.remove('mobile-menu-open');
586
+
587
+ // Reset icon
588
+ const searchIcon = searchBox.querySelector('i') as HTMLElement;
589
+ if (searchIcon) {
590
+ searchIcon.className = 'ti-search';
591
+ }
592
+
593
+ // Clear input
594
+ const searchField = searchInput.querySelector('input') as HTMLInputElement;
595
+ if (searchField) {
596
+ searchField.value = '';
597
+ searchField.blur();
598
+ }
599
+ }
600
+ }
601
+
602
+ /**
603
+ * Handle search query
604
+ */
605
+ private handleSearch(query: string): void {
606
+ this.log(`Search query: ${query}`);
607
+
608
+ // Implement your search logic here
609
+ // For demo, close search after "searching"
610
+ this.closeSearch();
611
+ }
612
+
613
+ /**
614
+ * Add component to the application
615
+ */
616
+ public addComponent(name: string, component: ComponentInterface): void {
617
+ this.state.components.set(name, component);
618
+ this.dispatchEvent('componentAdded', { name, component });
619
+ this.log(`Component added: ${name}`);
620
+ }
621
+
622
+ /**
623
+ * Remove component from the application
624
+ */
625
+ public removeComponent(name: string): void {
626
+ const component = this.state.components.get(name);
627
+ if (component) {
628
+ if (typeof component.destroy === 'function') {
629
+ component.destroy();
630
+ }
631
+ this.state.components.delete(name);
632
+ this.dispatchEvent('componentRemoved', { name });
633
+ this.log(`Component removed: ${name}`);
634
+ }
635
+ }
636
+
637
+ /**
638
+ * Get a component by name
639
+ */
640
+ public getComponent(name: string): ComponentInterface | undefined {
641
+ return this.state.components.get(name);
642
+ }
643
+
644
+ /**
645
+ * Get all components
646
+ */
647
+ public getComponents(): Map<string, ComponentInterface> {
648
+ return new Map(this.state.components);
649
+ }
650
+
651
+ /**
652
+ * Check if app is ready
653
+ */
654
+ public isReady(): boolean {
655
+ return this.state.isInitialized;
656
+ }
657
+
658
+ /**
659
+ * Get current application state
660
+ */
661
+ public getState(): Readonly<AdminatorAppState> {
662
+ return {
663
+ ...this.state,
664
+ components: new Map(this.state.components),
665
+ };
666
+ }
667
+
668
+ /**
669
+ * Update application options
670
+ */
671
+ public updateOptions(newOptions: Partial<AdminatorAppOptions>): void {
672
+ this.options = { ...this.options, ...newOptions };
673
+ this.log('Options updated');
674
+ }
675
+
676
+ /**
677
+ * Dispatch custom event
678
+ */
679
+ private dispatchEvent<T extends keyof AdminatorAppEvents>(
680
+ type: T,
681
+ detail: AdminatorAppEvents[T]['detail']
682
+ ): void {
683
+ const event = new CustomEvent(`adminator:${type}`, {
684
+ detail,
685
+ bubbles: true,
686
+ });
687
+ window.dispatchEvent(event);
688
+ }
689
+
690
+ /**
691
+ * Log message if debugging is enabled
692
+ */
693
+ private log(message: string): void {
694
+ if (this.options.debug) {
695
+ console.log(`[AdminatorApp] ${message}`);
696
+ }
697
+ }
698
+
699
+ /**
700
+ * Destroy the application
701
+ */
702
+ public destroy(): void {
703
+ this.log('Destroying Adminator App');
704
+
705
+ // Destroy all components
706
+ this.state.components.forEach((component, name) => {
707
+ if (typeof component.destroy === 'function') {
708
+ component.destroy();
709
+ }
710
+ this.log(`Component destroyed: ${name}`);
711
+ });
712
+
713
+ // Remove event listeners
714
+ this.eventHandlers.forEach((_, name) => {
715
+ // Note: We'd need to track which element each handler was attached to
716
+ // For now, we'll rely on the browser's garbage collection
717
+ this.log(`Event handler removed: ${name}`);
718
+ });
719
+
720
+ // Clear state
721
+ this.state.components.clear();
722
+ this.eventHandlers.clear();
723
+ this.state.isInitialized = false;
724
+
725
+ // Clear timeout
726
+ if (this.resizeTimeout) {
727
+ clearTimeout(this.resizeTimeout);
728
+ this.resizeTimeout = null;
729
+ }
730
+ }
731
+
732
+ /**
733
+ * Refresh/reinitialize the application
734
+ */
735
+ public refresh(): void {
736
+ this.log('Refreshing Adminator App');
737
+
738
+ if (this.state.isInitialized) {
739
+ this.destroy();
740
+ }
741
+
742
+ setTimeout(() => {
743
+ this.init();
744
+ }, 100);
745
+ }
746
+ }
747
+
748
+ // Initialize the application
749
+ const app = new AdminatorApp({
750
+ debug: process.env.NODE_ENV === 'development',
751
+ });
752
+
753
+ // Make app globally available for debugging
754
+ window.AdminatorApp = app;
755
+
756
+ // Export for module usage
757
+ export default app;