// ========================================== // CAROUSEL COMPONENT // ========================================== // Active session carousel with detailed task info and smooth transitions let carouselIndex = 0; let carouselSessions = []; let carouselInterval = null; let carouselPaused = false; const CAROUSEL_INTERVAL_MS = 5000; // 5 seconds function initCarousel() { const prevBtn = document.getElementById('carouselPrev'); const nextBtn = document.getElementById('carouselNext'); const pauseBtn = document.getElementById('carouselPause'); if (prevBtn) { prevBtn.addEventListener('click', () => { carouselPrev(); resetCarouselInterval(); }); } if (nextBtn) { nextBtn.addEventListener('click', () => { carouselNext(); resetCarouselInterval(); }); } if (pauseBtn) { pauseBtn.addEventListener('click', toggleCarouselPause); } } function updateCarousel() { // Get active sessions from workflowData const previousSessions = carouselSessions; const previousIndex = carouselIndex; const previousSessionId = previousSessions[previousIndex]?.session_id; carouselSessions = workflowData.activeSessions || []; // Try to preserve current position if (previousSessionId && carouselSessions.length > 0) { // Find if the same session still exists const newIndex = carouselSessions.findIndex(s => s.session_id === previousSessionId); if (newIndex !== -1) { carouselIndex = newIndex; } else if (previousIndex < carouselSessions.length) { // Keep same index if valid carouselIndex = previousIndex; } else { // Reset to last valid index carouselIndex = Math.max(0, carouselSessions.length - 1); } } else { carouselIndex = 0; } renderCarouselDots(); renderCarouselSlide('none'); startCarouselInterval(); } function renderCarouselDots() { const dotsContainer = document.getElementById('carouselDots'); if (!dotsContainer) return; if (carouselSessions.length === 0) { dotsContainer.innerHTML = ''; return; } dotsContainer.innerHTML = carouselSessions.map((_, index) => ` `).join(''); } function updateActiveDot() { const dots = document.querySelectorAll('.carousel-dot'); dots.forEach((dot, index) => { if (index === carouselIndex) { dot.classList.remove('bg-muted-foreground/40', 'hover:bg-muted-foreground/60', 'w-2'); dot.classList.add('bg-primary', 'w-4'); } else { dot.classList.remove('bg-primary', 'w-4'); dot.classList.add('bg-muted-foreground/40', 'hover:bg-muted-foreground/60', 'w-2'); } }); } function carouselGoToIndex(index) { if (index < 0 || index >= carouselSessions.length) return; const direction = index > carouselIndex ? 'left' : (index < carouselIndex ? 'right' : 'none'); carouselIndex = index; renderCarouselSlide(direction); updateActiveDot(); resetCarouselInterval(); } function renderCarouselSlide(direction = 'none') { const container = document.getElementById('carouselContent'); if (!container) return; if (carouselSessions.length === 0) { container.innerHTML = ` `; return; } const session = carouselSessions[carouselIndex]; const sessionType = session.type || 'workflow'; // Use simplified view for review sessions if (sessionType === 'review') { renderReviewCarouselSlide(container, session, direction); return; } const tasks = session.tasks || []; const completed = tasks.filter(t => t.status === 'completed').length; const inProgress = tasks.filter(t => t.status === 'in_progress').length; const pending = tasks.filter(t => t.status === 'pending').length; const taskCount = session.taskCount || tasks.length; const progress = taskCount > 0 ? Math.round((completed / taskCount) * 100) : 0; // Get session type badge const typeBadgeClass = getSessionTypeBadgeClass(sessionType); const sessionKey = `session-${session.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-'); // Animation class based on direction const animClass = direction === 'left' ? 'carousel-slide-left' : direction === 'right' ? 'carousel-slide-right' : 'carousel-fade-in'; // Get recent task activity const recentTasks = getRecentTaskActivity(tasks); // Format timestamps const createdTime = session.created_at ? formatRelativeTime(session.created_at) : ''; const updatedTime = session.updated_at ? formatRelativeTime(session.updated_at) : ''; // Get more tasks for display (up to 4) const displayTasks = getRecentTaskActivity(tasks, 4); container.innerHTML = ` `; // Store session data for navigation if (!sessionDataStore[sessionKey]) { sessionDataStore[sessionKey] = session; } } // Simplified carousel slide for review sessions function renderReviewCarouselSlide(container, session, direction) { const typeBadgeClass = getSessionTypeBadgeClass('review'); const sessionKey = `session-${session.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-'); const animClass = direction === 'left' ? 'carousel-slide-left' : direction === 'right' ? 'carousel-slide-right' : 'carousel-fade-in'; const createdTime = session.created_at ? formatRelativeTime(session.created_at) : ''; container.innerHTML = ` `; // Store session data for navigation if (!sessionDataStore[sessionKey]) { sessionDataStore[sessionKey] = session; } } function getSessionTypeBadgeClass(type) { const classes = { 'tdd': 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400', 'review': 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400', 'test': 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400', 'docs': 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', 'workflow': 'bg-primary-light text-primary' }; return classes[type] || classes['workflow']; } function getRecentTaskActivity(tasks, limit = 4) { if (!tasks || tasks.length === 0) return []; // Get in_progress tasks first, then most recently updated const sorted = [...tasks].sort((a, b) => { // in_progress first if (a.status === 'in_progress' && b.status !== 'in_progress') return -1; if (b.status === 'in_progress' && a.status !== 'in_progress') return 1; // Then by updated_at const timeA = a.updated_at || a.created_at || ''; const timeB = b.updated_at || b.created_at || ''; return timeB.localeCompare(timeA); }); // Return top N tasks return sorted.slice(0, limit); } function getTaskStatusEmoji(status) { const emojis = { 'completed': 'βœ…', 'in_progress': 'πŸ”„', 'pending': '⏸️', 'blocked': '🚫' }; return emojis[status] || 'πŸ“‹'; } function getTaskStatusIcon(status) { return status === 'in_progress' ? 'animate-spin-slow' : ''; } function formatRelativeTime(dateString) { if (!dateString) return ''; try { const date = new Date(dateString); const now = new Date(); const diffMs = now - date; const diffSecs = Math.floor(diffMs / 1000); const diffMins = Math.floor(diffSecs / 60); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffSecs < 60) return 'just now'; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; // Format as date for older return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } catch (e) { return dateString; } } function carouselNext() { if (carouselSessions.length === 0) return; carouselIndex = (carouselIndex + 1) % carouselSessions.length; renderCarouselSlide('left'); updateActiveDot(); } function carouselPrev() { if (carouselSessions.length === 0) return; carouselIndex = (carouselIndex - 1 + carouselSessions.length) % carouselSessions.length; renderCarouselSlide('right'); updateActiveDot(); } function startCarouselInterval() { stopCarouselInterval(); if (!carouselPaused && carouselSessions.length > 1) { carouselInterval = setInterval(carouselNext, CAROUSEL_INTERVAL_MS); } } function stopCarouselInterval() { if (carouselInterval) { clearInterval(carouselInterval); carouselInterval = null; } } function resetCarouselInterval() { if (!carouselPaused) { startCarouselInterval(); } } function toggleCarouselPause() { carouselPaused = !carouselPaused; const icon = document.getElementById('carouselPauseIcon'); if (carouselPaused) { stopCarouselInterval(); // Change to play icon if (icon) { icon.innerHTML = ''; } } else { startCarouselInterval(); // Change to pause icon if (icon) { icon.innerHTML = ''; } } } // Jump to specific session in carousel function carouselGoTo(sessionId) { const index = carouselSessions.findIndex(s => s.session_id === sessionId); if (index !== -1) { carouselIndex = index; renderCarouselSlide('none'); updateActiveDot(); resetCarouselInterval(); } }