claude-code-templates 1.10.1 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,441 @@
1
+ /**
2
+ * App - Main application component that handles routing and navigation
3
+ * Orchestrates the sidebar, dashboard, and agents pages
4
+ */
5
+ class App {
6
+ constructor(container, services) {
7
+ this.container = container;
8
+ this.services = services;
9
+
10
+ this.components = {};
11
+ this.currentPage = 'dashboard';
12
+ this.isInitialized = false;
13
+
14
+ this.init();
15
+ }
16
+
17
+ /**
18
+ * Initialize the application
19
+ */
20
+ async init() {
21
+ if (this.isInitialized) return;
22
+
23
+ try {
24
+ await this.render();
25
+ await this.initializeComponents();
26
+ await this.setupRouting();
27
+ this.bindEvents();
28
+ this.isInitialized = true;
29
+ } catch (error) {
30
+ console.error('Error initializing app:', error);
31
+ this.showError('Failed to initialize application');
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Render the application structure
37
+ */
38
+ async render() {
39
+ this.container.innerHTML = `
40
+ <div class="app">
41
+ <!-- Sidebar Navigation -->
42
+ <aside class="app-sidebar" id="app-sidebar">
43
+ <!-- Sidebar component will be mounted here -->
44
+ </aside>
45
+
46
+ <!-- Main Content Area -->
47
+ <main class="app-main" id="app-main">
48
+ <div class="app-content" id="app-content">
49
+ <!-- Page content will be mounted here -->
50
+ </div>
51
+ </main>
52
+
53
+ <!-- Global Loading Overlay -->
54
+ <div class="global-loading" id="global-loading" style="display: none;">
55
+ <div class="loading-content">
56
+ <div class="loading-spinner large"></div>
57
+ <span class="loading-text">Loading...</span>
58
+ </div>
59
+ </div>
60
+
61
+ <!-- Global Error Modal -->
62
+ <div class="error-modal" id="error-modal" style="display: none;">
63
+ <div class="error-modal-content">
64
+ <div class="error-modal-header">
65
+ <h3>Application Error</h3>
66
+ <button class="error-modal-close" id="error-modal-close">×</button>
67
+ </div>
68
+ <div class="error-modal-body">
69
+ <p class="error-modal-message" id="error-modal-message"></p>
70
+ </div>
71
+ <div class="error-modal-footer">
72
+ <button class="btn btn-primary" id="error-modal-retry">Retry</button>
73
+ <button class="btn btn-secondary" id="error-modal-dismiss">Dismiss</button>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ `;
79
+ }
80
+
81
+ /**
82
+ * Initialize child components
83
+ */
84
+ async initializeComponents() {
85
+ // Initialize Sidebar
86
+ const sidebarContainer = this.container.querySelector('#app-sidebar');
87
+ if (typeof Sidebar !== 'undefined') {
88
+ this.components.sidebar = new Sidebar(sidebarContainer, this.handleNavigation.bind(this));
89
+ } else {
90
+ throw new Error('Sidebar component not available. Check if components/Sidebar.js is loaded.');
91
+ }
92
+
93
+ // Initialize pages
94
+ this.components.pages = {};
95
+
96
+ // Load initial page
97
+ await this.loadPage(this.currentPage);
98
+ }
99
+
100
+ /**
101
+ * Setup routing (hash-based routing for SPA behavior)
102
+ */
103
+ setupRouting() {
104
+ // Handle browser navigation
105
+ window.addEventListener('hashchange', () => {
106
+ const hash = window.location.hash.slice(1) || 'dashboard';
107
+ this.navigateToPage(hash);
108
+ });
109
+
110
+ // Set initial route
111
+ const initialHash = window.location.hash.slice(1) || 'dashboard';
112
+ if (initialHash !== this.currentPage) {
113
+ this.navigateToPage(initialHash);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Bind global event listeners
119
+ */
120
+ bindEvents() {
121
+ // Sidebar toggle handler
122
+ window.addEventListener('sidebar-toggle', (event) => {
123
+ this.handleSidebarToggle(event.detail.collapsed);
124
+ });
125
+
126
+ // Error modal events
127
+ const errorModalClose = this.container.querySelector('#error-modal-close');
128
+ const errorModalDismiss = this.container.querySelector('#error-modal-dismiss');
129
+ const errorModalRetry = this.container.querySelector('#error-modal-retry');
130
+
131
+ errorModalClose.addEventListener('click', () => this.hideError());
132
+ errorModalDismiss.addEventListener('click', () => this.hideError());
133
+ errorModalRetry.addEventListener('click', () => this.retryLastAction());
134
+
135
+ // Global keyboard shortcuts
136
+ document.addEventListener('keydown', (e) => this.handleKeyboardShortcuts(e));
137
+ }
138
+
139
+ /**
140
+ * Handle navigation from sidebar
141
+ * @param {string} page - Page to navigate to
142
+ */
143
+ async handleNavigation(page) {
144
+ await this.navigateToPage(page);
145
+ }
146
+
147
+ /**
148
+ * Navigate to a specific page
149
+ * @param {string} page - Page to navigate to
150
+ */
151
+ async navigateToPage(page) {
152
+ if (page === this.currentPage) return;
153
+
154
+ try {
155
+ this.showGlobalLoading();
156
+
157
+ // Cleanup current page
158
+ await this.cleanupCurrentPage();
159
+
160
+ // Load new page
161
+ await this.loadPage(page);
162
+
163
+ // Update state
164
+ this.currentPage = page;
165
+
166
+ // Update URL hash
167
+ window.location.hash = page;
168
+
169
+ // Update sidebar active state
170
+ if (this.components.sidebar) {
171
+ this.components.sidebar.setActivePage(page);
172
+ }
173
+
174
+ } catch (error) {
175
+ console.error('Error navigating to page:', page, error);
176
+ this.showError(`Failed to load ${page} page`);
177
+ } finally {
178
+ this.hideGlobalLoading();
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Load a specific page
184
+ * @param {string} page - Page to load
185
+ */
186
+ async loadPage(page) {
187
+ const contentContainer = this.container.querySelector('#app-content');
188
+
189
+ if (!contentContainer) {
190
+ throw new Error('App content container not found');
191
+ }
192
+
193
+ console.log(`🔄 Loading page: ${page}`);
194
+
195
+ // First, destroy any existing page component for this page
196
+ if (this.components.pages[page] && this.components.pages[page].destroy) {
197
+ console.log(`🧹 Destroying existing ${page} page component`);
198
+ this.components.pages[page].destroy();
199
+ this.components.pages[page] = null;
200
+ }
201
+
202
+ // Always clear and recreate the page to ensure clean state
203
+ contentContainer.innerHTML = '';
204
+
205
+ // Create or recreate page component
206
+ await this.createPageComponent(page, contentContainer);
207
+
208
+ // Call showPage for any additional setup
209
+ await this.showPage(page);
210
+
211
+ console.log(`✅ Page '${page}' loaded successfully`);
212
+ }
213
+
214
+ /**
215
+ * Create page component
216
+ * @param {string} page - Page type
217
+ * @param {HTMLElement} container - Container element
218
+ */
219
+ async createPageComponent(page, container) {
220
+ // Cleanup existing page component if it exists
221
+ if (this.components.pages[page] && this.components.pages[page].destroy) {
222
+ this.components.pages[page].destroy();
223
+ }
224
+
225
+ switch (page) {
226
+ case 'dashboard':
227
+ if (typeof DashboardPage !== 'undefined') {
228
+ this.components.pages.dashboard = new DashboardPage(container, this.services);
229
+ await this.components.pages.dashboard.initialize();
230
+ } else {
231
+ throw new Error('DashboardPage component not available. Check if components/DashboardPage.js is loaded.');
232
+ }
233
+ break;
234
+
235
+ case 'agents':
236
+ if (typeof AgentsPage !== 'undefined') {
237
+ this.components.pages.agents = new AgentsPage(container, this.services);
238
+ await this.components.pages.agents.initialize();
239
+ // Expose agentsPage globally for modal access
240
+ if (typeof window !== 'undefined' && window.claudeAnalyticsApp) {
241
+ window.claudeAnalyticsApp.agentsPage = this.components.pages.agents;
242
+ console.log('✅ Exposed agentsPage globally for modal access');
243
+ }
244
+ } else {
245
+ throw new Error('AgentsPage component not available. Check if components/AgentsPage.js is loaded.');
246
+ }
247
+ break;
248
+
249
+ default:
250
+ throw new Error(`Unknown page: ${page}`);
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Show a specific page (simplified - loadPage handles everything)
256
+ * @param {string} page - Page to show
257
+ */
258
+ async showPage(page) {
259
+ // This method is now mainly for compatibility
260
+ // The actual work is done in loadPage
261
+ if (this.components.pages[page] && this.components.pages[page].onActivate) {
262
+ try {
263
+ await this.components.pages[page].onActivate();
264
+ } catch (error) {
265
+ console.warn(`Warning: onActivate failed for ${page}:`, error);
266
+ }
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Cleanup current page
272
+ */
273
+ async cleanupCurrentPage() {
274
+ // Clean up global references
275
+ if (this.currentPage === 'agents' && typeof window !== 'undefined' && window.claudeAnalyticsApp) {
276
+ window.claudeAnalyticsApp.agentsPage = undefined;
277
+ console.log('🧹 Cleaned up global agentsPage reference');
278
+ }
279
+
280
+ const currentPageComponent = this.components.pages[this.currentPage];
281
+ if (currentPageComponent && currentPageComponent.onDeactivate) {
282
+ await currentPageComponent.onDeactivate();
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Handle sidebar toggle
288
+ * @param {boolean} collapsed - Whether sidebar is collapsed
289
+ */
290
+ handleSidebarToggle(collapsed) {
291
+ const appMain = this.container.querySelector('#app-main');
292
+ appMain.classList.toggle('sidebar-collapsed', collapsed);
293
+ }
294
+
295
+ /**
296
+ * Handle keyboard shortcuts
297
+ * @param {KeyboardEvent} e - Keyboard event
298
+ */
299
+ handleKeyboardShortcuts(e) {
300
+ // Ctrl/Cmd + number keys for quick navigation
301
+ if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
302
+ switch (e.key) {
303
+ case '1':
304
+ e.preventDefault();
305
+ this.navigateToPage('dashboard');
306
+ break;
307
+ case '2':
308
+ e.preventDefault();
309
+ this.navigateToPage('agents');
310
+ break;
311
+ case 'r':
312
+ e.preventDefault();
313
+ this.refreshCurrentPage();
314
+ break;
315
+ }
316
+ }
317
+
318
+ // Escape key to close modals
319
+ if (e.key === 'Escape') {
320
+ this.hideError();
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Refresh current page
326
+ */
327
+ async refreshCurrentPage() {
328
+ const currentPageComponent = this.components.pages[this.currentPage];
329
+ if (currentPageComponent && currentPageComponent.refreshData) {
330
+ await currentPageComponent.refreshData();
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Show global loading
336
+ */
337
+ showGlobalLoading() {
338
+ const loadingOverlay = this.container.querySelector('#global-loading');
339
+ loadingOverlay.style.display = 'flex';
340
+ }
341
+
342
+ /**
343
+ * Hide global loading
344
+ */
345
+ hideGlobalLoading() {
346
+ const loadingOverlay = this.container.querySelector('#global-loading');
347
+ loadingOverlay.style.display = 'none';
348
+ }
349
+
350
+ /**
351
+ * Show error modal
352
+ * @param {string} message - Error message
353
+ */
354
+ showError(message) {
355
+ const errorModal = this.container.querySelector('#error-modal');
356
+ const errorMessage = this.container.querySelector('#error-modal-message');
357
+
358
+ errorMessage.textContent = message;
359
+ errorModal.style.display = 'flex';
360
+
361
+ this.lastError = message;
362
+ }
363
+
364
+ /**
365
+ * Hide error modal
366
+ */
367
+ hideError() {
368
+ const errorModal = this.container.querySelector('#error-modal');
369
+ errorModal.style.display = 'none';
370
+ }
371
+
372
+ /**
373
+ * Retry last action that caused an error
374
+ */
375
+ async retryLastAction() {
376
+ this.hideError();
377
+
378
+ try {
379
+ // Retry loading current page
380
+ await this.loadPage(this.currentPage);
381
+ } catch (error) {
382
+ console.error('Retry failed:', error);
383
+ this.showError('Retry failed. Please refresh the page.');
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Get current page name
389
+ * @returns {string} Current page name
390
+ */
391
+ getCurrentPage() {
392
+ return this.currentPage;
393
+ }
394
+
395
+ /**
396
+ * Get page component
397
+ * @param {string} page - Page name
398
+ * @returns {Object|null} Page component
399
+ */
400
+ getPageComponent(page) {
401
+ return this.components.pages[page] || null;
402
+ }
403
+
404
+ /**
405
+ * Check if page is loaded
406
+ * @param {string} page - Page name
407
+ * @returns {boolean} Whether page is loaded
408
+ */
409
+ isPageLoaded(page) {
410
+ return !!this.components.pages[page];
411
+ }
412
+
413
+ /**
414
+ * Destroy application
415
+ */
416
+ destroy() {
417
+ // Cleanup pages
418
+ Object.values(this.components.pages).forEach(page => {
419
+ if (page.destroy) {
420
+ page.destroy();
421
+ }
422
+ });
423
+
424
+ // Cleanup sidebar
425
+ if (this.components.sidebar && this.components.sidebar.destroy) {
426
+ this.components.sidebar.destroy();
427
+ }
428
+
429
+ // Remove event listeners
430
+ window.removeEventListener('hashchange', this.handleNavigation);
431
+ window.removeEventListener('sidebar-toggle', this.handleSidebarToggle);
432
+ document.removeEventListener('keydown', this.handleKeyboardShortcuts);
433
+
434
+ this.isInitialized = false;
435
+ }
436
+ }
437
+
438
+ // Export for module use
439
+ if (typeof module !== 'undefined' && module.exports) {
440
+ module.exports = App;
441
+ }
@@ -24,8 +24,8 @@ class Dashboard {
24
24
  if (this.isInitialized) return;
25
25
 
26
26
  try {
27
- this.stateService.setLoading(true);
28
27
  await this.render();
28
+ this.stateService.setLoading(true);
29
29
  await this.initializeComponents();
30
30
  await this.loadInitialData();
31
31
  this.startPeriodicRefresh();
@@ -419,7 +419,18 @@ class Dashboard {
419
419
  */
420
420
  updateLoadingState(isLoading) {
421
421
  const loadingOverlay = this.container.querySelector('#loading-overlay');
422
- loadingOverlay.style.display = isLoading ? 'flex' : 'none';
422
+ if (loadingOverlay) {
423
+ loadingOverlay.style.display = isLoading ? 'flex' : 'none';
424
+ } else {
425
+ // If the DOM isn't ready yet, show a temporary loading state
426
+ if (isLoading && this.container) {
427
+ this.container.style.opacity = '0.5';
428
+ this.container.style.pointerEvents = 'none';
429
+ } else if (this.container) {
430
+ this.container.style.opacity = '1';
431
+ this.container.style.pointerEvents = 'auto';
432
+ }
433
+ }
423
434
  }
424
435
 
425
436
  /**
@@ -430,11 +441,16 @@ class Dashboard {
430
441
  const errorBanner = this.container.querySelector('#error-banner');
431
442
  const errorText = this.container.querySelector('.error-text');
432
443
 
433
- if (error) {
434
- errorText.textContent = error.message || error;
435
- errorBanner.style.display = 'flex';
436
- } else {
437
- errorBanner.style.display = 'none';
444
+ if (errorBanner && errorText) {
445
+ if (error) {
446
+ errorText.textContent = error.message || error;
447
+ errorBanner.style.display = 'flex';
448
+ } else {
449
+ errorBanner.style.display = 'none';
450
+ }
451
+ } else if (error) {
452
+ // If DOM isn't ready, log error for now
453
+ console.error('Dashboard error (DOM not ready):', error.message || error);
438
454
  }
439
455
  }
440
456