claude-code-templates
Version:
CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects
307 lines (272 loc) • 9.2 kB
JavaScript
/**
* HeaderComponent - Unified header for all analytics pages
* Provides consistent branding, navigation, and controls across the application
*/
class HeaderComponent {
constructor(container, options = {}) {
this.container = container;
this.options = {
title: options.title || 'Claude Code Analytics Dashboard',
subtitle: options.subtitle || 'Real-time monitoring and analytics for Claude Code sessions',
showVersionBadge: options.showVersionBadge !== false,
showLastUpdate: options.showLastUpdate !== false,
showThemeSwitch: options.showThemeSwitch !== false,
showGitHubLink: options.showGitHubLink !== false,
version: options.version || 'v1.13.2', // Default fallback
customActions: options.customActions || [],
dataService: options.dataService || null, // DataService for dynamic version loading
...options
};
this.lastUpdateTime = null;
this.updateInterval = null;
this.actualVersion = this.options.version; // Will be updated dynamically
}
/**
* Render the header component
*/
render() {
const headerHTML = `
<div class="page-header">
<div class="header-content">
<div class="header-left">
<div class="status-header">
<span class="session-timer-status-dot active" id="session-status-dot"></span>
<h1 class="page-title">
${this.options.title}
${this.options.showVersionBadge ? `<span class="version-badge" id="version-badge">${this.actualVersion}</span>` : ''}
</h1>
</div>
<div class="page-subtitle">
${this.options.subtitle}
</div>
${this.options.showLastUpdate ? `
<div class="last-update-header">
<span class="last-update-label">last update:</span>
<span id="last-update-header-text">Never</span>
</div>
` : ''}
</div>
<div class="header-right">
${this.options.showThemeSwitch ? `
<div class="theme-switch-container" title="Toggle light/dark theme">
<div class="theme-switch" id="header-theme-switch">
<div class="theme-switch-track">
<div class="theme-switch-thumb" id="header-theme-switch-thumb">
<span class="theme-switch-icon">🌙</span>
</div>
</div>
</div>
</div>
` : ''}
${this.options.showGitHubLink ? `
<a href="https://github.com/davila7/claude-code-templates" target="_blank" class="github-link" title="Star on GitHub">
<span class="github-icon">⭐</span>
Star on GitHub
</a>
` : ''}
${this.renderCustomActions()}
</div>
</div>
</div>
`;
this.container.innerHTML = headerHTML;
this.bindEvents();
this.initializeTheme();
if (this.options.showLastUpdate) {
this.updateLastUpdateTime();
this.startUpdateInterval();
}
// Load version dynamically if DataService is available
if (this.options.dataService) {
this.loadVersion();
}
}
/**
* Render custom actions in header
*/
renderCustomActions() {
if (!this.options.customActions || this.options.customActions.length === 0) {
return '';
}
return this.options.customActions.map(action => `
<button class="header-action-btn" id="${action.id}" title="${action.title || action.label}">
${action.icon ? `<span class="btn-icon">${action.icon}</span>` : ''}
${action.label}
</button>
`).join('');
}
/**
* Bind event listeners
*/
bindEvents() {
// Theme toggle
if (this.options.showThemeSwitch) {
const themeSwitch = this.container.querySelector('#header-theme-switch');
if (themeSwitch) {
themeSwitch.addEventListener('click', () => this.toggleTheme());
}
}
// Custom action handlers
this.options.customActions.forEach(action => {
const btn = this.container.querySelector(`#${action.id}`);
if (btn && action.handler) {
btn.addEventListener('click', action.handler);
}
});
}
/**
* Initialize theme from localStorage
*/
initializeTheme() {
if (!this.options.showThemeSwitch) return;
const savedTheme = localStorage.getItem('claude-analytics-theme') || 'dark';
const body = document.body;
const headerThumb = this.container.querySelector('#header-theme-switch-thumb');
const headerIcon = headerThumb?.querySelector('.theme-switch-icon');
body.setAttribute('data-theme', savedTheme);
if (headerThumb && headerIcon) {
if (savedTheme === 'light') {
headerThumb.classList.add('light');
headerIcon.textContent = '☀️';
} else {
headerThumb.classList.remove('light');
headerIcon.textContent = '🌙';
}
}
}
/**
* Toggle theme between light and dark
*/
toggleTheme() {
const body = document.body;
const headerThumb = this.container.querySelector('#header-theme-switch-thumb');
const headerIcon = headerThumb?.querySelector('.theme-switch-icon');
// Also sync with global theme switch if exists
const globalThumb = document.getElementById('themeSwitchThumb');
const globalIcon = globalThumb?.querySelector('.theme-switch-icon');
const isLight = body.getAttribute('data-theme') === 'light';
const newTheme = isLight ? 'dark' : 'light';
body.setAttribute('data-theme', newTheme);
// Update header theme switch
if (headerThumb && headerIcon) {
headerThumb.classList.toggle('light', newTheme === 'light');
headerIcon.textContent = newTheme === 'light' ? '☀️' : '🌙';
}
// Sync with global theme switch
if (globalThumb && globalIcon) {
globalThumb.classList.toggle('light', newTheme === 'light');
globalIcon.textContent = newTheme === 'light' ? '☀️' : '🌙';
}
localStorage.setItem('claude-analytics-theme', newTheme);
}
/**
* Update last update time
*/
updateLastUpdateTime() {
if (!this.options.showLastUpdate) return;
const currentTime = new Date().toLocaleTimeString();
const lastUpdateText = this.container.querySelector('#last-update-header-text');
if (lastUpdateText) {
lastUpdateText.textContent = currentTime;
}
this.lastUpdateTime = currentTime;
}
/**
* Start periodic update of timestamp
*/
startUpdateInterval() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
}
// Update every 30 seconds
this.updateInterval = setInterval(() => {
this.updateLastUpdateTime();
}, 30000);
}
/**
* Stop update interval
*/
stopUpdateInterval() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
}
/**
* Load version dynamically from backend
*/
async loadVersion() {
if (!this.options.dataService) return;
try {
const versionData = await this.options.dataService.getVersion();
if (versionData && versionData.version) {
this.actualVersion = `v${versionData.version}`;
this.updateVersionBadge();
}
} catch (error) {
console.error('Error loading version:', error);
}
}
/**
* Update version badge in the DOM
*/
updateVersionBadge() {
const versionBadge = this.container.querySelector('#version-badge');
if (versionBadge) {
versionBadge.textContent = this.actualVersion;
}
}
/**
* Update header title
* @param {string} title - New title
*/
updateTitle(title) {
this.options.title = title;
const titleElement = this.container.querySelector('.page-title');
if (titleElement) {
titleElement.innerHTML = `
${title}
${this.options.showVersionBadge ? `<span class="version-badge" id="version-badge">${this.actualVersion}</span>` : ''}
`;
}
}
/**
* Update header subtitle
* @param {string} subtitle - New subtitle
*/
updateSubtitle(subtitle) {
this.options.subtitle = subtitle;
const subtitleElement = this.container.querySelector('.page-subtitle');
if (subtitleElement) {
subtitleElement.textContent = subtitle;
}
}
/**
* Update status dot state
* @param {boolean} active - Whether status should be active
*/
updateStatusDot(active) {
const statusDot = this.container.querySelector('#session-status-dot');
if (statusDot) {
statusDot.classList.toggle('active', active);
}
}
/**
* Get current theme
* @returns {string} Current theme ('light' or 'dark')
*/
getCurrentTheme() {
return document.body.getAttribute('data-theme') || 'dark';
}
/**
* Destroy header component
*/
destroy() {
this.stopUpdateInterval();
this.container.innerHTML = '';
}
}
// Export for module use
if (typeof module !== 'undefined' && module.exports) {
module.exports = HeaderComponent;
}