claude-code-templates 1.12.2 → 1.13.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.
@@ -1,589 +0,0 @@
1
- /**
2
- * Dashboard - Main dashboard component that orchestrates all other components
3
- * Part of the modular frontend architecture
4
- */
5
- class Dashboard {
6
- constructor(container, services) {
7
- this.container = container;
8
- this.dataService = services.data;
9
- this.stateService = services.state;
10
- this.chartService = services.chart;
11
-
12
- this.components = {};
13
- this.refreshInterval = null;
14
- this.isInitialized = false;
15
-
16
- // Subscribe to state changes
17
- this.unsubscribe = this.stateService.subscribe(this.handleStateChange.bind(this));
18
- }
19
-
20
- /**
21
- * Initialize the dashboard
22
- */
23
- async initialize() {
24
- if (this.isInitialized) return;
25
-
26
- try {
27
- await this.render();
28
- this.stateService.setLoading(true);
29
- await this.initializeComponents();
30
- await this.loadInitialData();
31
- this.startPeriodicRefresh();
32
- this.isInitialized = true;
33
- } catch (error) {
34
- console.error('Error initializing dashboard:', error);
35
- this.stateService.setError(error);
36
- } finally {
37
- this.stateService.setLoading(false);
38
- }
39
- }
40
-
41
- /**
42
- * Handle state changes from StateService
43
- * @param {Object} state - New state
44
- * @param {string} action - Action that caused the change
45
- */
46
- handleStateChange(state, action) {
47
- switch (action) {
48
- case 'update_conversations':
49
- this.updateSummaryDisplay(state.summary);
50
- break;
51
- case 'update_conversation_states':
52
- this.updateConversationStates(state.conversationStates);
53
- break;
54
- case 'set_loading':
55
- this.updateLoadingState(state.isLoading);
56
- break;
57
- case 'set_error':
58
- this.updateErrorState(state.error);
59
- break;
60
- case 'conversation_state_change':
61
- this.handleConversationStateChange(state);
62
- break;
63
- }
64
- }
65
-
66
- /**
67
- * Render the dashboard structure
68
- */
69
- async render() {
70
- this.container.innerHTML = `
71
- <div class="dashboard-container">
72
- <!-- Header -->
73
- <div class="dashboard-header">
74
- <div class="terminal-title">
75
- <span class="status-dot"></span>
76
- Claude Code Analytics - Terminal
77
- </div>
78
- <div class="header-controls">
79
- <button class="control-btn" id="refresh-btn" title="Refresh data">
80
- <span class="refresh-icon">🔄</span>
81
- Refresh
82
- </button>
83
- <button class="control-btn" id="notifications-btn" title="Toggle notifications">
84
- <span class="notification-icon">🔔</span>
85
- Notifications
86
- </button>
87
- <div class="last-update">
88
- <span id="last-update-text">Never</span>
89
- </div>
90
- </div>
91
- </div>
92
-
93
- <!-- Loading State -->
94
- <div class="loading-overlay" id="loading-overlay" style="display: none;">
95
- <div class="loading-spinner"></div>
96
- <span class="loading-text">Loading dashboard...</span>
97
- </div>
98
-
99
- <!-- Error State -->
100
- <div class="error-banner" id="error-banner" style="display: none;">
101
- <span class="error-icon">⚠️</span>
102
- <span class="error-text"></span>
103
- <button class="error-close" id="error-close">×</button>
104
- </div>
105
-
106
- <!-- Summary Cards -->
107
- <div class="summary-section">
108
- <div class="summary-cards">
109
- <div class="summary-card">
110
- <div class="card-header">
111
- <h3>Total Conversations</h3>
112
- <span class="card-icon">💬</span>
113
- </div>
114
- <div class="card-value" id="total-conversations">0</div>
115
- <div class="card-change">
116
- <span class="change-text" id="conversations-change">+0 today</span>
117
- </div>
118
- </div>
119
-
120
- <div class="summary-card">
121
- <div class="card-header">
122
- <h3>Active Sessions</h3>
123
- <span class="card-icon">⚡</span>
124
- </div>
125
- <div class="card-value" id="active-sessions">0</div>
126
- <div class="card-change">
127
- <span class="change-text" id="sessions-change">0 running</span>
128
- </div>
129
- </div>
130
-
131
- <div class="summary-card">
132
- <div class="card-header">
133
- <h3>Total Tokens</h3>
134
- <span class="card-icon">🔤</span>
135
- </div>
136
- <div class="card-value" id="total-tokens">0</div>
137
- <div class="card-change">
138
- <span class="change-text" id="tokens-change">0 avg/conv</span>
139
- </div>
140
- </div>
141
-
142
- <div class="summary-card">
143
- <div class="card-header">
144
- <h3>Projects</h3>
145
- <span class="card-icon">📁</span>
146
- </div>
147
- <div class="card-value" id="total-projects">0</div>
148
- <div class="card-change">
149
- <span class="change-text" id="projects-change">0 active</span>
150
- </div>
151
- </div>
152
- </div>
153
- </div>
154
-
155
- <!-- Session Timer Section -->
156
- <div class="session-timer-section">
157
- <div id="session-timer-container">
158
- <!-- SessionTimer component will be mounted here -->
159
- </div>
160
- </div>
161
-
162
- <!-- Charts Section -->
163
- <div class="charts-section">
164
- <div class="chart-container">
165
- <div class="chart-header">
166
- <h3>Usage Over Time</h3>
167
- <div class="chart-controls">
168
- <select id="chart-period">
169
- <option value="7d">Last 7 days</option>
170
- <option value="30d">Last 30 days</option>
171
- <option value="90d">Last 90 days</option>
172
- </select>
173
- </div>
174
- </div>
175
- <div class="chart-canvas-container">
176
- <canvas id="usage-chart"></canvas>
177
- </div>
178
- </div>
179
- </div>
180
-
181
- <!-- Conversations Table -->
182
- <div class="conversations-section">
183
- <div class="section-header">
184
- <h3>Recent Conversations</h3>
185
- <div class="section-controls">
186
- <button class="control-btn" id="export-btn">
187
- <span class="export-icon">📤</span>
188
- Export
189
- </button>
190
- </div>
191
- </div>
192
- <div class="conversations-table-container" id="conversations-table">
193
- <!-- ConversationTable component will be mounted here -->
194
- </div>
195
- </div>
196
-
197
- <!-- Footer -->
198
- <div class="dashboard-footer">
199
- <div class="footer-info">
200
- <span>Analytics Dashboard v1.0</span>
201
- <span>•</span>
202
- <span id="connection-status">Connected</span>
203
- </div>
204
- </div>
205
- </div>
206
- `;
207
-
208
- this.bindEvents();
209
- }
210
-
211
- /**
212
- * Initialize child components
213
- */
214
- async initializeComponents() {
215
- // Initialize SessionTimer
216
- const sessionTimerContainer = this.container.querySelector('#session-timer-container');
217
- if (sessionTimerContainer && window.SessionTimer) {
218
- this.components.sessionTimer = new SessionTimer(
219
- sessionTimerContainer,
220
- this.dataService,
221
- this.stateService
222
- );
223
- await this.components.sessionTimer.initialize();
224
- }
225
-
226
- // Initialize ConversationTable
227
- const tableContainer = this.container.querySelector('#conversations-table');
228
- this.components.conversationTable = new ConversationTable(
229
- tableContainer,
230
- this.dataService,
231
- this.stateService
232
- );
233
- await this.components.conversationTable.initialize();
234
-
235
- // Initialize Charts (if available)
236
- if (this.chartService) {
237
- await this.initializeCharts();
238
- }
239
- }
240
-
241
- /**
242
- * Initialize charts
243
- */
244
- async initializeCharts() {
245
- const chartCanvas = this.container.querySelector('#usage-chart');
246
- if (chartCanvas) {
247
- this.components.usageChart = new Chart(chartCanvas, {
248
- type: 'line',
249
- data: {
250
- labels: [],
251
- datasets: [{
252
- label: 'Conversations',
253
- data: [],
254
- borderColor: '#3fb950',
255
- backgroundColor: 'rgba(63, 185, 80, 0.1)',
256
- tension: 0.4
257
- }]
258
- },
259
- options: {
260
- responsive: true,
261
- maintainAspectRatio: false,
262
- scales: {
263
- y: {
264
- beginAtZero: true,
265
- grid: {
266
- color: '#30363d'
267
- },
268
- ticks: {
269
- color: '#c9d1d9'
270
- }
271
- },
272
- x: {
273
- grid: {
274
- color: '#30363d'
275
- },
276
- ticks: {
277
- color: '#c9d1d9'
278
- }
279
- }
280
- },
281
- plugins: {
282
- legend: {
283
- labels: {
284
- color: '#c9d1d9'
285
- }
286
- }
287
- }
288
- }
289
- });
290
- }
291
- }
292
-
293
- /**
294
- * Bind event listeners
295
- */
296
- bindEvents() {
297
- // Refresh button
298
- const refreshBtn = this.container.querySelector('#refresh-btn');
299
- refreshBtn.addEventListener('click', () => this.refreshData());
300
-
301
- // Notifications button
302
- const notificationsBtn = this.container.querySelector('#notifications-btn');
303
- notificationsBtn.addEventListener('click', () => this.toggleNotifications());
304
-
305
- // Export button
306
- const exportBtn = this.container.querySelector('#export-btn');
307
- exportBtn.addEventListener('click', () => this.exportData());
308
-
309
- // Chart period selector
310
- const chartPeriod = this.container.querySelector('#chart-period');
311
- chartPeriod.addEventListener('change', (e) => this.updateChartPeriod(e.target.value));
312
-
313
- // Error banner close
314
- const errorClose = this.container.querySelector('#error-close');
315
- errorClose.addEventListener('click', () => this.clearError());
316
- }
317
-
318
- /**
319
- * Load initial data
320
- */
321
- async loadInitialData() {
322
- try {
323
- const [conversationsData, statesData] = await Promise.all([
324
- this.dataService.getConversations(),
325
- this.dataService.getConversationStates()
326
- ]);
327
-
328
- this.stateService.updateConversations(conversationsData.conversations);
329
- this.stateService.updateSummary(conversationsData.summary);
330
- this.stateService.updateConversationStates(statesData);
331
-
332
- this.updateLastUpdateTime();
333
- } catch (error) {
334
- console.error('Error loading initial data:', error);
335
- this.stateService.setError('Failed to load dashboard data');
336
- }
337
- }
338
-
339
- /**
340
- * Refresh all data
341
- */
342
- async refreshData() {
343
- const refreshBtn = this.container.querySelector('#refresh-btn');
344
- refreshBtn.disabled = true;
345
- refreshBtn.querySelector('.refresh-icon').style.animation = 'spin 1s linear infinite';
346
-
347
- try {
348
- this.dataService.clearCache();
349
- await this.loadInitialData();
350
- } catch (error) {
351
- console.error('Error refreshing data:', error);
352
- this.stateService.setError('Failed to refresh data');
353
- } finally {
354
- refreshBtn.disabled = false;
355
- refreshBtn.querySelector('.refresh-icon').style.animation = '';
356
- }
357
- }
358
-
359
- /**
360
- * Update summary display
361
- * @param {Object} summary - Summary data
362
- */
363
- updateSummaryDisplay(summary) {
364
- if (!summary) return;
365
-
366
- const totalConvs = this.container.querySelector('#total-conversations');
367
- const activeSessions = this.container.querySelector('#active-sessions');
368
- const totalTokens = this.container.querySelector('#total-tokens');
369
- const totalProjects = this.container.querySelector('#total-projects');
370
-
371
- if (totalConvs) totalConvs.textContent = summary.totalConversations?.toLocaleString() || '0';
372
- if (activeSessions) activeSessions.textContent = summary.activeConversations?.toLocaleString() || '0';
373
- if (totalTokens) totalTokens.textContent = summary.totalTokens?.toLocaleString() || '0';
374
- if (totalProjects) totalProjects.textContent = summary.totalProjects?.toLocaleString() || '0';
375
-
376
- // Update change indicators
377
- const conversationsChange = this.container.querySelector('#conversations-change');
378
- const sessionsChange = this.container.querySelector('#sessions-change');
379
- const tokensChange = this.container.querySelector('#tokens-change');
380
- const projectsChange = this.container.querySelector('#projects-change');
381
-
382
- if (conversationsChange) conversationsChange.textContent = `+${summary.totalConversations || 0} total`;
383
- if (sessionsChange) sessionsChange.textContent = `${summary.activeConversations || 0} running`;
384
- if (tokensChange) tokensChange.textContent = `${summary.avgTokensPerConversation || 0} avg/conv`;
385
- if (projectsChange) projectsChange.textContent = `${summary.activeProjects || 0} active`;
386
- }
387
-
388
- /**
389
- * Update conversation states
390
- * @param {Object} states - Conversation states
391
- */
392
- updateConversationStates(states) {
393
- // Update status dot based on active conversations
394
- const statusDot = this.container.querySelector('.status-dot');
395
- const activeCount = Object.values(states).filter(state => state === 'active').length;
396
-
397
- if (activeCount > 0) {
398
- statusDot.style.background = '#3fb950';
399
- statusDot.style.animation = 'pulse 2s infinite';
400
- } else {
401
- statusDot.style.background = '#f85149';
402
- statusDot.style.animation = 'none';
403
- }
404
- }
405
-
406
- /**
407
- * Handle conversation state change
408
- * @param {Object} state - New state
409
- */
410
- handleConversationStateChange(state) {
411
- // Show notification for state changes
412
- this.showNotification(`Conversation state changed`, 'info');
413
- this.updateLastUpdateTime();
414
- }
415
-
416
- /**
417
- * Update loading state
418
- * @param {boolean} isLoading - Loading state
419
- */
420
- updateLoadingState(isLoading) {
421
- const loadingOverlay = this.container.querySelector('#loading-overlay');
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
- }
434
- }
435
-
436
- /**
437
- * Update error state
438
- * @param {Error|string} error - Error object or message
439
- */
440
- updateErrorState(error) {
441
- const errorBanner = this.container.querySelector('#error-banner');
442
- const errorText = this.container.querySelector('.error-text');
443
-
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);
454
- }
455
- }
456
-
457
- /**
458
- * Clear error state
459
- */
460
- clearError() {
461
- this.stateService.clearError();
462
- }
463
-
464
- /**
465
- * Toggle notifications
466
- */
467
- toggleNotifications() {
468
- const notificationsBtn = this.container.querySelector('#notifications-btn');
469
- const isEnabled = notificationsBtn.classList.toggle('active');
470
-
471
- if (isEnabled) {
472
- this.requestNotificationPermission();
473
- }
474
- }
475
-
476
- /**
477
- * Request notification permission
478
- */
479
- async requestNotificationPermission() {
480
- if ('Notification' in window) {
481
- const permission = await Notification.requestPermission();
482
- if (permission === 'granted') {
483
- this.showNotification('Notifications enabled', 'success');
484
- }
485
- }
486
- }
487
-
488
- /**
489
- * Show notification
490
- * @param {string} message - Notification message
491
- * @param {string} type - Notification type
492
- */
493
- showNotification(message, type = 'info') {
494
- if ('Notification' in window && Notification.permission === 'granted') {
495
- new Notification(`Claude Code Analytics`, {
496
- body: message,
497
- icon: '/favicon.ico'
498
- });
499
- }
500
- }
501
-
502
- /**
503
- * Export data
504
- */
505
- exportData() {
506
- const conversations = this.stateService.getStateProperty('conversations');
507
- const dataStr = JSON.stringify(conversations, null, 2);
508
- const dataBlob = new Blob([dataStr], { type: 'application/json' });
509
- const url = URL.createObjectURL(dataBlob);
510
-
511
- const link = document.createElement('a');
512
- link.href = url;
513
- link.download = `claude-analytics-${new Date().toISOString().split('T')[0]}.json`;
514
- link.click();
515
-
516
- URL.revokeObjectURL(url);
517
- }
518
-
519
- /**
520
- * Update chart period
521
- * @param {string} period - New period
522
- */
523
- updateChartPeriod(period) {
524
- // Update chart with new period data
525
- console.log('Updating chart period to:', period);
526
- // Implementation would update the chart with filtered data
527
- }
528
-
529
- /**
530
- * Update last update time
531
- */
532
- updateLastUpdateTime() {
533
- const lastUpdateText = this.container.querySelector('#last-update-text');
534
- if (lastUpdateText) {
535
- lastUpdateText.textContent = new Date().toLocaleTimeString();
536
- }
537
- }
538
-
539
- /**
540
- * Start periodic refresh
541
- */
542
- startPeriodicRefresh() {
543
- this.refreshInterval = setInterval(async () => {
544
- try {
545
- const statesData = await this.dataService.getConversationStates();
546
- this.stateService.updateConversationStates(statesData);
547
- this.updateLastUpdateTime();
548
- } catch (error) {
549
- console.error('Error during periodic refresh:', error);
550
- }
551
- }, 30000); // Refresh every 30 seconds
552
- }
553
-
554
- /**
555
- * Stop periodic refresh
556
- */
557
- stopPeriodicRefresh() {
558
- if (this.refreshInterval) {
559
- clearInterval(this.refreshInterval);
560
- this.refreshInterval = null;
561
- }
562
- }
563
-
564
- /**
565
- * Cleanup and destroy dashboard
566
- */
567
- destroy() {
568
- this.stopPeriodicRefresh();
569
-
570
- // Cleanup components
571
- Object.values(this.components).forEach(component => {
572
- if (component.destroy) {
573
- component.destroy();
574
- }
575
- });
576
-
577
- // Unsubscribe from state changes
578
- if (this.unsubscribe) {
579
- this.unsubscribe();
580
- }
581
-
582
- this.isInitialized = false;
583
- }
584
- }
585
-
586
- // Export for module use
587
- if (typeof module !== 'undefined' && module.exports) {
588
- module.exports = Dashboard;
589
- }