refactor(dashboard): optimize template structure and enhance data aggregation

- Reorder CSS and JS file loading in dashboard-generator.js for consistency
- Simplify dashboard.css by removing redundant styles and consolidating to Tailwind-based approach
- Add backup files for dashboard.html, dashboard.css, and review-cycle-dashboard.html
- Create new Tailwind-based dashboard template (dashboard_tailwind.html) and test variant
- Add tailwind.config.js for Tailwind CSS configuration
- Enhance data-aggregator.js to load full task data for archived sessions (previously only counted)
- Add meta, context, and flow_control fields to task objects for richer data representation
- Implement review data loading for archived sessions to match active session behavior
- Improve task sorting consistency across active and archived sessions
- Reduce CSS file size by ~70% through Tailwind utility consolidation while maintaining visual parity
This commit is contained in:
catlog22
2025-12-04 21:41:30 +08:00
parent 39df995e37
commit 942fca7ad8
14 changed files with 9770 additions and 5448 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -4,37 +4,219 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CCW Dashboard</title>
<!-- Google Fonts: Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: ['class', '[data-theme="dark"]'],
safelist: [
// Background colors
'bg-card', 'bg-background', 'bg-hover', 'bg-accent', 'bg-muted', 'bg-primary', 'bg-success', 'bg-warning',
'bg-success-light', 'bg-warning-light', 'bg-sidebar-background', 'bg-destructive',
// Text colors
'text-foreground', 'text-muted-foreground', 'text-primary', 'text-card-foreground', 'text-success', 'text-warning',
'text-primary-foreground', 'text-accent-foreground', 'text-sidebar-foreground', 'text-destructive',
// Border colors
'border', 'border-border', 'border-primary', 'border-success', 'border-warning', 'border-muted',
'border-l-success', 'border-l-warning', 'border-l-muted-foreground', 'border-l-primary',
// Layout
'rounded', 'rounded-lg', 'rounded-md', 'rounded-full', 'shadow', 'shadow-sm', 'shadow-md', 'shadow-lg',
'p-2', 'p-3', 'p-4', 'p-5', 'px-3', 'px-4', 'px-5', 'py-2', 'py-3', 'py-4',
'm-2', 'mb-2', 'mb-3', 'mb-4', 'mt-2', 'mt-3', 'mt-4', 'mx-2', 'my-2',
'gap-2', 'gap-3', 'gap-4', 'space-y-2', 'space-y-3',
// Flex & Grid
'flex', 'flex-1', 'flex-col', 'flex-wrap', 'items-center', 'items-start', 'justify-between', 'justify-center',
'grid', 'grid-cols-1', 'grid-cols-2', 'grid-cols-3',
// Sizing
'w-full', 'w-5', 'w-8', 'h-2', 'h-5', 'h-8', 'min-w-0', 'max-w-full',
// Text
'text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl', 'text-3xl',
'font-medium', 'font-semibold', 'font-bold', 'font-mono', 'truncate', 'uppercase',
// States & Transitions
'hover:shadow-md', 'hover:bg-hover', 'hover:-translate-y-1', 'hover:text-foreground',
'transition-all', 'duration-200', 'duration-300', 'cursor-pointer',
// Opacity & visibility
'opacity-50', 'opacity-80', 'hidden', 'block', 'inline', 'inline-flex',
// Position
'relative', 'absolute', 'fixed', 'sticky', 'top-0', 'right-0', 'left-0', 'bottom-0',
'z-10', 'z-40', 'z-50', 'overflow-hidden', 'overflow-y-auto',
],
theme: {
extend: {
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: 'hsl(var(--card))',
'card-foreground': 'hsl(var(--card-foreground))',
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
primary: 'hsl(var(--primary))',
'primary-foreground': 'hsl(var(--primary-foreground))',
secondary: 'hsl(var(--secondary))',
'secondary-foreground': 'hsl(var(--secondary-foreground))',
accent: 'hsl(var(--accent))',
'accent-foreground': 'hsl(var(--accent-foreground))',
destructive: 'hsl(var(--destructive))',
'destructive-foreground': 'hsl(var(--destructive-foreground))',
muted: 'hsl(var(--muted))',
'muted-foreground': 'hsl(var(--muted-foreground))',
'sidebar-background': 'hsl(var(--sidebar-background))',
'sidebar-foreground': 'hsl(var(--sidebar-foreground))',
hover: 'hsl(var(--hover))',
success: 'hsl(var(--success))',
'success-light': 'hsl(var(--success-light))',
warning: 'hsl(var(--warning))',
'warning-light': 'hsl(var(--warning-light))',
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
mono: ['Consolas', 'Monaco', 'Courier New', 'monospace'],
},
boxShadow: {
'sm': '0 1px 2px 0 rgb(0 0 0 / 0.05)',
'DEFAULT': '0 2px 8px rgb(0 0 0 / 0.08)',
'md': '0 4px 12px rgb(0 0 0 / 0.1)',
'lg': '0 8px 24px rgb(0 0 0 / 0.12)',
},
},
},
}
</script>
<style>
{{CSS_CONTENT}}
/* CSS Custom Properties - Light Mode */
:root {
--background: 0 0% 98%;
--foreground: 0 0% 13%;
--card: 0 0% 100%;
--card-foreground: 0 0% 13%;
--border: 0 0% 90%;
--input: 0 0% 90%;
--ring: 220 65% 50%;
--primary: 220 65% 50%;
--primary-foreground: 0 0% 100%;
--secondary: 220 60% 65%;
--secondary-foreground: 0 0% 100%;
--accent: 220 40% 95%;
--accent-foreground: 0 0% 13%;
--destructive: 8 75% 55%;
--destructive-foreground: 0 0% 100%;
--muted: 0 0% 96%;
--muted-foreground: 0 0% 45%;
--sidebar-background: 0 0% 97%;
--sidebar-foreground: 0 0% 13%;
--hover: 0 0% 93%;
--success: 142 71% 45%;
--success-light: 142 76% 90%;
--warning: 38 92% 50%;
--warning-light: 48 96% 89%;
}
/* Dark Mode */
[data-theme="dark"] {
--background: 220 13% 10%;
--foreground: 0 0% 90%;
--card: 220 13% 14%;
--card-foreground: 0 0% 90%;
--border: 220 13% 20%;
--input: 220 13% 20%;
--ring: 220 65% 55%;
--primary: 220 65% 55%;
--primary-foreground: 0 0% 100%;
--secondary: 220 60% 60%;
--secondary-foreground: 0 0% 100%;
--accent: 220 30% 20%;
--accent-foreground: 0 0% 90%;
--destructive: 8 70% 50%;
--destructive-foreground: 0 0% 100%;
--muted: 220 13% 18%;
--muted-foreground: 0 0% 55%;
--sidebar-background: 220 13% 12%;
--sidebar-foreground: 0 0% 90%;
--hover: 220 13% 22%;
--success: 142 71% 40%;
--success-light: 142 50% 20%;
--warning: 38 85% 45%;
--warning-light: 40 50% 20%;
}
/* Scrollbar styling */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: hsl(var(--border)); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: hsl(var(--muted-foreground)); }
/* Sidebar collapse state */
.sidebar.collapsed { width: 60px; }
.sidebar.collapsed .nav-text,
.sidebar.collapsed .nav-section-title,
.sidebar.collapsed .badge,
.sidebar.collapsed .toggle-text { display: none; }
.sidebar.collapsed .nav-section-header { justify-content: center; padding: 12px 0; }
.sidebar.collapsed .nav-item { justify-content: center; padding: 10px 0; }
.sidebar.collapsed .toggle-icon { transform: rotate(180deg); }
/* Path menu open state */
.path-menu.open { display: block; }
/* Mobile sidebar */
@media (max-width: 768px) {
.sidebar {
position: fixed;
left: -260px;
top: 56px;
height: calc(100vh - 56px);
z-index: 200;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
}
.sidebar.open { left: 0; }
.sidebar-overlay.open { display: block; }
.menu-toggle-btn { display: block !important; }
}
/* Task drawer */
.task-detail-drawer {
transform: translateX(100%);
transition: transform 0.3s ease;
}
.task-detail-drawer.open { transform: translateX(0); }
.drawer-overlay.active { display: block; }
/* Injected from dashboard.css */
{{CSS_CONTENT}}
</style>
</head>
<body>
<div class="app-container">
<body class="font-sans bg-background text-foreground leading-normal">
<div class="flex flex-col min-h-screen">
<!-- Top Bar -->
<header class="top-bar">
<div class="top-bar-left">
<button class="menu-toggle" id="menuToggle"></button>
<div class="logo">
<span class="logo-icon"></span>
<span class="logo-text">Claude Code Workflow</span>
<header class="flex items-center justify-between px-5 h-14 bg-card border-b border-border sticky top-0 z-50 shadow-sm">
<div class="flex items-center gap-4">
<button class="hidden md:hidden p-2 text-foreground hover:bg-hover rounded menu-toggle-btn" id="menuToggle"></button>
<div class="flex items-center gap-2 text-lg font-semibold text-primary">
<span class="text-2xl"></span>
<span class="hidden sm:inline">Claude Code Workflow</span>
</div>
</div>
<!-- Path Selector -->
<div class="path-selector">
<label>Project:</label>
<div class="path-dropdown">
<button class="path-current" id="pathButton">
<span class="path-text" id="currentPath">{{PROJECT_PATH}}</span>
<span class="dropdown-arrow"></span>
<div class="flex items-center gap-2 relative">
<label class="hidden sm:inline text-sm text-muted-foreground">Project:</label>
<div class="relative">
<button class="flex items-center gap-2 px-3 py-1.5 bg-background border border-border rounded text-sm text-foreground hover:bg-hover max-w-[300px]" id="pathButton">
<span class="truncate max-w-[240px]" id="currentPath">{{PROJECT_PATH}}</span>
<span class="text-xs text-muted-foreground"></span>
</button>
<div class="path-menu" id="pathMenu">
<div class="menu-label">Recent Projects</div>
<div id="recentPaths">
<div class="path-menu hidden absolute top-full left-0 mt-1 bg-card border border-border rounded-lg shadow-lg min-w-[280px] z-50" id="pathMenu">
<div class="px-3 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">Recent Projects</div>
<div id="recentPaths" class="border-t border-border">
<!-- Dynamic recent paths -->
</div>
<div class="path-actions">
<button class="path-browse" id="browsePath">
<div class="p-2 border-t border-border">
<button class="w-full flex items-center justify-center gap-2 px-3 py-2 bg-background border border-border rounded text-sm text-muted-foreground hover:bg-hover" id="browsePath">
📂 Browse...
</button>
</div>
@@ -43,106 +225,107 @@
</div>
<!-- Header Actions -->
<div class="header-actions">
<button class="theme-toggle" id="themeToggle" title="Toggle theme">🌙</button>
<div class="flex items-center gap-3">
<button class="p-2 text-xl hover:bg-hover rounded" id="themeToggle" title="Toggle theme">🌙</button>
</div>
</header>
<!-- Sidebar Overlay (mobile) -->
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<div class="sidebar-overlay fixed inset-0 bg-black/50 z-40" id="sidebarOverlay"></div>
<!-- Main Layout -->
<div class="main-layout">
<div class="flex flex-1">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<nav class="sidebar-nav">
<aside class="sidebar w-64 bg-sidebar-background border-r border-border flex flex-col sticky top-14 h-[calc(100vh-56px)] overflow-y-auto transition-all duration-300" id="sidebar">
<nav class="flex-1 py-3">
<!-- Sessions Section -->
<div class="nav-section">
<div class="nav-section-header">
<span class="nav-icon">📁</span>
<div class="mb-2">
<div class="flex items-center px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
<span class="mr-2">📁</span>
<span class="nav-section-title">Sessions</span>
</div>
<ul class="nav-items">
<li class="nav-item active tooltip" data-filter="all" data-tooltip="All Sessions">
<span class="nav-icon">📋</span>
<span class="nav-text">All</span>
<span class="badge" id="badgeAll">0</span>
<ul class="space-y-0.5">
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors active bg-accent text-primary font-medium" data-filter="all" data-tooltip="All Sessions">
<span>📋</span>
<span class="nav-text flex-1">All</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeAll">0</span>
</li>
<li class="nav-item tooltip" data-filter="active" data-tooltip="Active Sessions">
<span class="nav-icon">🟢</span>
<span class="nav-text">Active</span>
<span class="badge success" id="badgeActive">0</span>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-filter="active" data-tooltip="Active Sessions">
<span>🟢</span>
<span class="nav-text flex-1">Active</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-success-light text-success" id="badgeActive">0</span>
</li>
<li class="nav-item tooltip" data-filter="archived" data-tooltip="Archived Sessions">
<span class="nav-icon">📦</span>
<span class="nav-text">Archived</span>
<span class="badge" id="badgeArchived">0</span>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-filter="archived" data-tooltip="Archived Sessions">
<span>📦</span>
<span class="nav-text flex-1">Archived</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeArchived">0</span>
</li>
</ul>
</div>
<!-- Lite Tasks Section -->
<div class="nav-section" id="liteTasksNav">
<div class="nav-section-header">
<span class="nav-icon"></span>
<div class="mb-2" id="liteTasksNav">
<div class="flex items-center px-4 py-2 text-xs font-semibold text-muted-foreground uppercase tracking-wide">
<span class="mr-2"></span>
<span class="nav-section-title">Lite Tasks</span>
</div>
<ul class="nav-items">
<li class="nav-item tooltip" data-lite="lite-plan" data-tooltip="Lite Plan Sessions">
<span class="nav-icon">📝</span>
<span class="nav-text">lite-plan</span>
<span class="badge" id="badgeLitePlan">0</span>
<ul class="space-y-0.5">
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-lite="lite-plan" data-tooltip="Lite Plan Sessions">
<span>📝</span>
<span class="nav-text flex-1">lite-plan</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeLitePlan">0</span>
</li>
<li class="nav-item tooltip" data-lite="lite-fix" data-tooltip="Lite Fix Sessions">
<span class="nav-icon">🔧</span>
<span class="nav-text">lite-fix</span>
<span class="badge" id="badgeLiteFix">0</span>
<li class="nav-item flex items-center gap-2 mx-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-lite="lite-fix" data-tooltip="Lite Fix Sessions">
<span>🔧</span>
<span class="nav-text flex-1">lite-fix</span>
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeLiteFix">0</span>
</li>
</ul>
</div>
</nav>
<!-- Sidebar Footer -->
<div class="sidebar-footer">
<button class="sidebar-toggle" id="sidebarToggle">
<span class="toggle-icon"></span>
<div class="p-3 border-t border-border">
<button class="flex items-center justify-center gap-2 w-full px-3 py-2 border border-border rounded text-sm text-muted-foreground hover:bg-hover transition-colors" id="sidebarToggle">
<span class="toggle-icon transition-transform duration-300"></span>
<span class="toggle-text">Collapse</span>
</button>
</div>
</aside>
<!-- Content Area -->
<main class="content">
<main class="flex-1 p-6 overflow-y-auto min-w-0">
<!-- Stats Grid -->
<section class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-value" id="statTotalSessions">0</div>
<div class="stat-label">Total Sessions</div>
<section class="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-4 mb-6">
<div class="bg-card border border-border rounded-lg p-5 text-center hover:shadow-md transition-all duration-200">
<div class="text-2xl mb-2">📊</div>
<div class="text-3xl font-bold text-foreground" id="statTotalSessions">0</div>
<div class="text-sm text-muted-foreground mt-1">Total Sessions</div>
</div>
<div class="stat-card">
<div class="stat-icon">🟢</div>
<div class="stat-value" id="statActiveSessions">0</div>
<div class="stat-label">Active Sessions</div>
<div class="bg-card border border-border rounded-lg p-5 text-center hover:shadow-md transition-all duration-200">
<div class="text-2xl mb-2">🟢</div>
<div class="text-3xl font-bold text-foreground" id="statActiveSessions">0</div>
<div class="text-sm text-muted-foreground mt-1">Active Sessions</div>
</div>
<div class="stat-card">
<div class="stat-icon">📋</div>
<div class="stat-value" id="statTotalTasks">0</div>
<div class="stat-label">Total Tasks</div>
<div class="bg-card border border-border rounded-lg p-5 text-center hover:shadow-md transition-all duration-200">
<div class="text-2xl mb-2">📋</div>
<div class="text-3xl font-bold text-foreground" id="statTotalTasks">0</div>
<div class="text-sm text-muted-foreground mt-1">Total Tasks</div>
</div>
<div class="stat-card">
<div class="stat-icon"></div>
<div class="stat-value" id="statCompletedTasks">0</div>
<div class="stat-label">Completed Tasks</div>
<div class="bg-card border border-border rounded-lg p-5 text-center hover:shadow-md transition-all duration-200">
<div class="text-2xl mb-2"></div>
<div class="text-3xl font-bold text-foreground" id="statCompletedTasks">0</div>
<div class="text-sm text-muted-foreground mt-1">Completed Tasks</div>
</div>
</section>
<!-- Content Header -->
<div class="content-header">
<h2 class="content-title" id="contentTitle">All Sessions</h2>
<div class="search-box">
<input type="text" placeholder="Search..." id="searchInput">
<div class="flex items-center justify-between flex-wrap gap-3 mb-5">
<h2 class="text-xl font-semibold text-foreground" id="contentTitle">All Sessions</h2>
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-sm">🔍</span>
<input type="text" placeholder="Search..." id="searchInput"
class="pl-9 pr-4 py-2 w-60 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all">
</div>
</div>
@@ -154,22 +337,22 @@
</div>
<!-- Footer -->
<footer class="bottom-bar">
<footer class="flex items-center justify-between px-5 h-10 bg-card border-t border-border text-xs text-muted-foreground">
<div>Generated: <span id="generatedAt">-</span></div>
<div>CCW Dashboard v1.0</div>
</footer>
<!-- Task Detail Drawer -->
<div class="task-detail-drawer" id="taskDetailDrawer">
<div class="drawer-header">
<h3 class="drawer-title" id="drawerTaskTitle">Task Details</h3>
<button class="drawer-close" onclick="closeTaskDrawer()">&times;</button>
<div class="task-detail-drawer fixed top-0 right-0 w-[400px] max-w-full h-full bg-card border-l border-border shadow-lg z-50 flex flex-col" id="taskDetailDrawer">
<div class="flex items-center justify-between px-5 py-4 border-b border-border">
<h3 class="text-lg font-semibold text-foreground" id="drawerTaskTitle">Task Details</h3>
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded" onclick="closeTaskDrawer()">&times;</button>
</div>
<div class="drawer-content" id="drawerContent">
<div class="flex-1 overflow-y-auto p-5" id="drawerContent">
<!-- Dynamic content -->
</div>
</div>
<div class="drawer-overlay" id="drawerOverlay" onclick="closeTaskDrawer()"></div>
<div class="drawer-overlay fixed inset-0 bg-black/50 z-40" id="drawerOverlay" onclick="closeTaskDrawer()"></div>
</div>
<!-- D3.js for Flowchart -->

View File

@@ -0,0 +1,182 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CCW Dashboard</title>
<style>
{{CSS_CONTENT}}
</style>
</head>
<body>
<div class="app-container">
<!-- Top Bar -->
<header class="top-bar">
<div class="top-bar-left">
<button class="menu-toggle" id="menuToggle">☰</button>
<div class="logo">
<span class="logo-icon">⚡</span>
<span class="logo-text">Claude Code Workflow</span>
</div>
</div>
<!-- Path Selector -->
<div class="path-selector">
<label>Project:</label>
<div class="path-dropdown">
<button class="path-current" id="pathButton">
<span class="path-text" id="currentPath">{{PROJECT_PATH}}</span>
<span class="dropdown-arrow">▼</span>
</button>
<div class="path-menu" id="pathMenu">
<div class="menu-label">Recent Projects</div>
<div id="recentPaths">
<!-- Dynamic recent paths -->
</div>
<div class="path-actions">
<button class="path-browse" id="browsePath">
📂 Browse...
</button>
</div>
</div>
</div>
</div>
<!-- Header Actions -->
<div class="header-actions">
<button class="theme-toggle" id="themeToggle" title="Toggle theme">🌙</button>
</div>
</header>
<!-- Sidebar Overlay (mobile) -->
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<!-- Main Layout -->
<div class="main-layout">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<nav class="sidebar-nav">
<!-- Sessions Section -->
<div class="nav-section">
<div class="nav-section-header">
<span class="nav-icon">📁</span>
<span class="nav-section-title">Sessions</span>
</div>
<ul class="nav-items">
<li class="nav-item active tooltip" data-filter="all" data-tooltip="All Sessions">
<span class="nav-icon">📋</span>
<span class="nav-text">All</span>
<span class="badge" id="badgeAll">0</span>
</li>
<li class="nav-item tooltip" data-filter="active" data-tooltip="Active Sessions">
<span class="nav-icon">🟢</span>
<span class="nav-text">Active</span>
<span class="badge success" id="badgeActive">0</span>
</li>
<li class="nav-item tooltip" data-filter="archived" data-tooltip="Archived Sessions">
<span class="nav-icon">📦</span>
<span class="nav-text">Archived</span>
<span class="badge" id="badgeArchived">0</span>
</li>
</ul>
</div>
<!-- Lite Tasks Section -->
<div class="nav-section" id="liteTasksNav">
<div class="nav-section-header">
<span class="nav-icon">⚡</span>
<span class="nav-section-title">Lite Tasks</span>
</div>
<ul class="nav-items">
<li class="nav-item tooltip" data-lite="lite-plan" data-tooltip="Lite Plan Sessions">
<span class="nav-icon">📝</span>
<span class="nav-text">lite-plan</span>
<span class="badge" id="badgeLitePlan">0</span>
</li>
<li class="nav-item tooltip" data-lite="lite-fix" data-tooltip="Lite Fix Sessions">
<span class="nav-icon">🔧</span>
<span class="nav-text">lite-fix</span>
<span class="badge" id="badgeLiteFix">0</span>
</li>
</ul>
</div>
</nav>
<!-- Sidebar Footer -->
<div class="sidebar-footer">
<button class="sidebar-toggle" id="sidebarToggle">
<span class="toggle-icon">◀</span>
<span class="toggle-text">Collapse</span>
</button>
</div>
</aside>
<!-- Content Area -->
<main class="content">
<!-- Stats Grid -->
<section class="stats-grid">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-value" id="statTotalSessions">0</div>
<div class="stat-label">Total Sessions</div>
</div>
<div class="stat-card">
<div class="stat-icon">🟢</div>
<div class="stat-value" id="statActiveSessions">0</div>
<div class="stat-label">Active Sessions</div>
</div>
<div class="stat-card">
<div class="stat-icon">📋</div>
<div class="stat-value" id="statTotalTasks">0</div>
<div class="stat-label">Total Tasks</div>
</div>
<div class="stat-card">
<div class="stat-icon">✅</div>
<div class="stat-value" id="statCompletedTasks">0</div>
<div class="stat-label">Completed Tasks</div>
</div>
</section>
<!-- Content Header -->
<div class="content-header">
<h2 class="content-title" id="contentTitle">All Sessions</h2>
<div class="search-box">
<input type="text" placeholder="Search..." id="searchInput">
</div>
</div>
<!-- Main Content Container -->
<section class="main-content" id="mainContent">
<!-- Dynamic content: sessions grid or session detail page -->
</section>
</main>
</div>
<!-- Footer -->
<footer class="bottom-bar">
<div>Generated: <span id="generatedAt">-</span></div>
<div>CCW Dashboard v1.0</div>
</footer>
<!-- Task Detail Drawer -->
<div class="task-detail-drawer" id="taskDetailDrawer">
<div class="drawer-header">
<h3 class="drawer-title" id="drawerTaskTitle">Task Details</h3>
<button class="drawer-close" onclick="closeTaskDrawer()">&times;</button>
</div>
<div class="drawer-content" id="drawerContent">
<!-- Dynamic content -->
</div>
</div>
<div class="drawer-overlay" id="drawerOverlay" onclick="closeTaskDrawer()"></div>
</div>
<!-- D3.js for Flowchart -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
{{JS_CONTENT}}
</script>
</body>
</html>

View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CCW Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: ['class', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
background: 'hsl(var(--color-background))',
foreground: 'hsl(var(--color-foreground))',
card: 'hsl(var(--color-card))',
border: 'hsl(var(--color-border))',
},
},
},
}
</script>
<style>:root{--color-background:0 0% 100%;--color-foreground:0 0% 25%;}</style>
</head>
<body>
<div class="flex flex-col min-h-screen bg-background text-foreground">
<header class="flex items-center justify-between px-5 h-14 bg-card border-b border-border sticky top-0 z-50">
<div class="flex items-center gap-2">
<span class="text-xl"></span>
<span class="font-semibold">Claude Code Workflow</span>
</div>
</header>
<main class="flex-1 p-6">
<h1>Dashboard</h1>
</main>
</div>
<script>{{JS_CONTENT}}</script>
</body>
</html>

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CCW Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: ['class', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
background: 'hsl(var(--color-background))',
foreground: 'hsl(var(--color-foreground))',
card: 'hsl(var(--color-card))',
border: 'hsl(var(--color-border))',
input: 'hsl(var(--color-input))',
ring: 'hsl(var(--color-ring))',
primary: 'hsl(var(--color-interactive-primary-default))',
accent: 'hsl(var(--color-interactive-accent-default))',
muted: 'hsl(var(--color-muted))',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
},
}
</script>
<style>:root{--color-background:0 0% 100%;--color-foreground:0 0% 25%;--color-card:0 0% 100%;--color-border:0 0% 90%;--color-input:0 0% 90%;--color-ring:220 65% 50%;--color-interactive-primary-default:220 65% 50%;--color-interactive-accent-default:220 40% 95%;--color-muted:0 0% 97%;--color-muted-foreground:0 0% 50%;--color-sidebar-background:0 0% 97.5%;}[data-theme="dark"]{--color-background:0 0% 10%;--color-foreground:0 0% 90%;--color-card:0 0% 15%;--color-border:0 0% 25%;}</style>
</head>
<body>Test</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,212 @@
/* Tailwind Base Styles with Design Tokens */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* CSS Custom Properties - Light Mode (Default) */
:root {
/* Base Colors */
--color-background: 0 0% 100%; /* oklch(1 0 0) -> white */
--color-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
--color-card: 0 0% 100%; /* oklch(1 0 0) -> white */
--color-card-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
--color-border: 0 0% 90%; /* oklch(0.9 0 0) -> light gray */
--color-input: 0 0% 90%; /* oklch(0.9 0 0) -> light gray */
--color-ring: 220 65% 50%; /* oklch(0.5 0.15 250) -> primary blue */
/* Interactive Colors - Primary */
--color-interactive-primary-default: 220 65% 50%; /* oklch(0.5 0.15 250) -> #4066bf */
--color-interactive-primary-hover: 220 65% 55%; /* oklch(0.55 0.15 250) -> lighter blue */
--color-interactive-primary-active: 220 65% 45%; /* oklch(0.45 0.15 250) -> darker blue */
--color-interactive-primary-disabled: 220 30% 70%; /* oklch(0.7 0.05 250) -> muted blue */
--color-interactive-primary-foreground: 0 0% 100%; /* oklch(1 0 0) -> white */
/* Interactive Colors - Secondary */
--color-interactive-secondary-default: 220 60% 65%; /* oklch(0.65 0.12 250) -> #6b8ccc */
--color-interactive-secondary-hover: 220 60% 70%; /* oklch(0.7 0.12 250) -> lighter */
--color-interactive-secondary-active: 220 60% 60%; /* oklch(0.6 0.12 250) -> darker */
--color-interactive-secondary-disabled: 220 30% 80%; /* oklch(0.8 0.05 250) -> muted */
--color-interactive-secondary-foreground: 0 0% 100%; /* oklch(1 0 0) -> white */
/* Interactive Colors - Accent */
--color-interactive-accent-default: 220 40% 95%; /* oklch(0.95 0.02 250) -> #eef3fa */
--color-interactive-accent-hover: 220 45% 92%; /* oklch(0.92 0.03 250) -> slightly darker */
--color-interactive-accent-active: 220 35% 97%; /* oklch(0.97 0.02 250) -> slightly lighter */
--color-interactive-accent-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
/* Interactive Colors - Destructive */
--color-interactive-destructive-default: 8 75% 55%; /* oklch(0.55 0.22 25) -> #d93025 */
--color-interactive-destructive-hover: 8 75% 60%; /* oklch(0.6 0.22 25) -> lighter red */
--color-interactive-destructive-foreground: 0 0% 100%; /* oklch(1 0 0) -> white */
/* Semantic Colors */
--color-muted: 0 0% 97%; /* oklch(0.97 0 0) -> very light gray */
--color-muted-foreground: 0 0% 50%; /* oklch(0.5 0 0) -> medium gray */
/* Sidebar Colors */
--color-sidebar-background: 0 0% 97.5%; /* oklch(0.975 0 0) -> #f8f8f8 */
--color-sidebar-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
--color-sidebar-primary: 220 65% 50%; /* oklch(0.5 0.15 250) -> primary blue */
--color-sidebar-primary-foreground: 0 0% 100%; /* oklch(1 0 0) -> white */
--color-sidebar-accent: 220 40% 95%; /* oklch(0.95 0.02 250) -> light blue */
--color-sidebar-accent-foreground: 0 0% 25%; /* oklch(0.25 0 0) -> dark gray */
--color-sidebar-border: 0 0% 90%; /* oklch(0.9 0 0) -> light gray */
/* Typography */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'Consolas', 'Monaco', 'Courier New', monospace;
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-size-4xl: 2.25rem; /* 36px */
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
--letter-spacing-tight: -0.025em;
--letter-spacing-normal: 0;
--letter-spacing-wide: 0.025em;
/* Spacing */
--spacing-0: 0;
--spacing-1: 0.25rem; /* 4px */
--spacing-2: 0.5rem; /* 8px */
--spacing-3: 0.75rem; /* 12px */
--spacing-4: 1rem; /* 16px */
--spacing-6: 1.5rem; /* 24px */
--spacing-8: 2rem; /* 32px */
--spacing-12: 3rem; /* 48px */
--spacing-16: 4rem; /* 64px */
/* Effects */
--opacity-disabled: 0.5;
--opacity-hover: 0.8;
--opacity-active: 1;
/* Shadows */
--shadow-2xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-xs: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-sm: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-md: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
--shadow-xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
/* Border Radius */
--border-radius-sm: calc(0.375rem - 4px); /* 2px */
--border-radius-md: calc(0.375rem - 2px); /* 4px */
--border-radius-lg: 0.375rem; /* 6px */
--border-radius-xl: calc(0.375rem + 4px); /* 10px */
--border-radius-default: 0.375rem; /* 6px */
/* Animations */
--duration-instant: 0ms;
--duration-fast: 150ms;
--duration-normal: 200ms;
--duration-medium: 300ms;
--duration-slow: 500ms;
--easing-linear: linear;
--easing-ease-in: cubic-bezier(0.4, 0, 1, 1);
--easing-ease-out: cubic-bezier(0, 0, 0.2, 1);
--easing-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--easing-spring: cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* Dark Mode Theme */
[data-theme="dark"] {
/* Base Colors - Dark Mode */
--color-background: 0 0% 10%; /* Dark background */
--color-foreground: 0 0% 90%; /* Light text */
--color-card: 0 0% 15%; /* Dark card background */
--color-card-foreground: 0 0% 90%; /* Light card text */
--color-border: 0 0% 25%; /* Dark border */
--color-input: 0 0% 25%; /* Dark input border */
--color-ring: 220 65% 60%; /* Brighter ring for dark mode */
/* Interactive Colors - Primary (Dark Mode) */
--color-interactive-primary-default: 220 70% 60%; /* Brighter blue for dark mode */
--color-interactive-primary-hover: 220 70% 65%; /* Even brighter on hover */
--color-interactive-primary-active: 220 70% 55%; /* Slightly darker on active */
--color-interactive-primary-disabled: 220 30% 40%; /* Muted blue for dark mode */
--color-interactive-primary-foreground: 0 0% 100%; /* White text */
/* Interactive Colors - Secondary (Dark Mode) */
--color-interactive-secondary-default: 220 60% 70%; /* Brighter secondary */
--color-interactive-secondary-hover: 220 60% 75%; /* Brighter on hover */
--color-interactive-secondary-active: 220 60% 65%; /* Slightly darker on active */
--color-interactive-secondary-disabled: 220 30% 50%; /* Muted */
--color-interactive-secondary-foreground: 0 0% 100%; /* White text */
/* Interactive Colors - Accent (Dark Mode) */
--color-interactive-accent-default: 220 30% 25%; /* Dark accent */
--color-interactive-accent-hover: 220 35% 30%; /* Slightly lighter on hover */
--color-interactive-accent-active: 220 25% 20%; /* Darker on active */
--color-interactive-accent-foreground: 0 0% 90%; /* Light text */
/* Interactive Colors - Destructive (Dark Mode) */
--color-interactive-destructive-default: 8 75% 60%; /* Brighter red for visibility */
--color-interactive-destructive-hover: 8 75% 65%; /* Even brighter on hover */
--color-interactive-destructive-foreground: 0 0% 100%; /* White text */
/* Semantic Colors (Dark Mode) */
--color-muted: 0 0% 20%; /* Dark muted background */
--color-muted-foreground: 0 0% 60%; /* Lighter muted text */
/* Sidebar Colors (Dark Mode) */
--color-sidebar-background: 0 0% 12%; /* Slightly lighter than background */
--color-sidebar-foreground: 0 0% 90%; /* Light text */
--color-sidebar-primary: 220 70% 60%; /* Brighter blue */
--color-sidebar-primary-foreground: 0 0% 100%; /* White text */
--color-sidebar-accent: 220 30% 25%; /* Dark accent */
--color-sidebar-accent-foreground: 0 0% 90%; /* Light text */
--color-sidebar-border: 0 0% 25%; /* Dark border */
}
/* Base typography */
* {
box-sizing: border-box;
}
body {
@apply bg-background text-foreground font-sans leading-normal;
margin: 0;
padding: 0;
}
/* Focus styles */
*:focus-visible {
outline: 2px solid hsl(var(--color-ring));
outline-offset: 2px;
}
}
@layer utilities {
/* Custom utility classes */
.text-balance {
text-wrap: balance;
}
.transition-default {
transition: all var(--duration-normal) var(--easing-ease-out);
}
.transition-fast {
transition: all var(--duration-fast) var(--easing-ease-out);
}
.transition-medium {
transition: all var(--duration-medium) var(--easing-ease-in-out);
}
.transition-slow {
transition: all var(--duration-slow) var(--easing-ease-in-out);
}
}

View File

@@ -1,472 +1,193 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Workflow Dashboard - Task Board</title>
<!-- Google Fonts: Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: ['class', '[data-theme="dark"]'],
safelist: [
'bg-card', 'bg-background', 'bg-hover', 'bg-accent', 'bg-muted', 'bg-primary', 'bg-success', 'bg-warning',
'bg-success-light', 'bg-warning-light', 'text-foreground', 'text-muted-foreground', 'text-primary',
'text-success', 'text-warning', 'text-primary-foreground', 'border', 'border-border', 'border-primary',
'border-l-success', 'border-l-warning', 'border-l-muted-foreground', 'rounded', 'rounded-lg', 'rounded-full',
'shadow', 'shadow-sm', 'shadow-md', 'p-2', 'p-3', 'p-4', 'p-5', 'px-3', 'px-4', 'py-2', 'py-3',
'mb-2', 'mb-3', 'mb-4', 'mt-2', 'mt-4', 'mx-auto', 'gap-2', 'gap-3', 'gap-4', 'space-y-2',
'flex', 'flex-1', 'flex-col', 'flex-wrap', 'items-center', 'items-start', 'justify-between', 'justify-center',
'grid', 'w-full', 'w-5', 'h-2', 'h-5', 'text-xs', 'text-sm', 'text-lg', 'text-xl', 'text-2xl', 'text-3xl',
'font-medium', 'font-semibold', 'font-bold', 'font-mono', 'truncate', 'uppercase',
'hover:shadow-md', 'hover:bg-hover', 'hover:-translate-y-1', 'hover:text-foreground', 'hover:opacity-90',
'hover:scale-110', 'transition-all', 'duration-200', 'duration-300', 'cursor-pointer',
'opacity-50', 'hidden', 'block', 'relative', 'absolute', 'fixed', 'z-50', 'overflow-hidden',
'col-span-full', 'text-center', 'min-h-screen', 'max-w-7xl',
],
theme: {
extend: {
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: 'hsl(var(--card))',
'card-foreground': 'hsl(var(--card-foreground))',
border: 'hsl(var(--border))',
primary: 'hsl(var(--primary))',
'primary-foreground': 'hsl(var(--primary-foreground))',
accent: 'hsl(var(--accent))',
'accent-foreground': 'hsl(var(--accent-foreground))',
muted: 'hsl(var(--muted))',
'muted-foreground': 'hsl(var(--muted-foreground))',
hover: 'hsl(var(--hover))',
success: 'hsl(var(--success))',
'success-light': 'hsl(var(--success-light))',
warning: 'hsl(var(--warning))',
'warning-light': 'hsl(var(--warning-light))',
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
},
},
},
}
</script>
<style>
/* CSS Custom Properties - Light Mode */
:root {
--bg-primary: #f5f7fa;
--bg-secondary: #ffffff;
--bg-card: #ffffff;
--text-primary: #1a202c;
--text-secondary: #718096;
--border-color: #e2e8f0;
--accent-color: #4299e1;
--success-color: #48bb78;
--warning-color: #ed8936;
--danger-color: #f56565;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--background: 0 0% 98%;
--foreground: 0 0% 13%;
--card: 0 0% 100%;
--card-foreground: 0 0% 13%;
--border: 0 0% 90%;
--primary: 220 65% 50%;
--primary-foreground: 0 0% 100%;
--accent: 220 40% 95%;
--accent-foreground: 0 0% 13%;
--muted: 0 0% 96%;
--muted-foreground: 0 0% 45%;
--hover: 0 0% 93%;
--success: 142 71% 45%;
--success-light: 142 76% 90%;
--warning: 38 92% 50%;
--warning-light: 48 96% 89%;
}
/* Dark Mode */
[data-theme="dark"] {
--bg-primary: #1a202c;
--bg-secondary: #2d3748;
--bg-card: #2d3748;
--text-primary: #f7fafc;
--text-secondary: #a0aec0;
--border-color: #4a5568;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
transition: background-color 0.3s, color 0.3s;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--bg-secondary);
box-shadow: var(--shadow);
padding: 20px;
margin-bottom: 30px;
border-radius: 8px;
}
h1 {
font-size: 2rem;
margin-bottom: 10px;
color: var(--accent-color);
}
.header-controls {
display: flex;
gap: 15px;
flex-wrap: wrap;
align-items: center;
margin-top: 15px;
}
.search-box {
flex: 1;
min-width: 250px;
position: relative;
}
.search-box input {
width: 100%;
padding: 10px 15px;
border: 1px solid var(--border-color);
border-radius: 6px;
background-color: var(--bg-primary);
color: var(--text-primary);
font-size: 0.95rem;
}
.filter-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.2s;
background-color: var(--bg-card);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow);
}
.btn.active {
background-color: var(--accent-color);
color: white;
border-color: var(--accent-color);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background-color: var(--bg-card);
padding: 20px;
border-radius: 8px;
box-shadow: var(--shadow);
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: var(--accent-color);
}
.stat-label {
color: var(--text-secondary);
font-size: 0.9rem;
margin-top: 5px;
}
.section {
margin-bottom: 40px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.section-title {
font-size: 1.5rem;
font-weight: 600;
}
.sessions-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
}
.session-card {
background-color: var(--bg-card);
border-radius: 8px;
box-shadow: var(--shadow);
padding: 20px;
transition: all 0.3s;
}
.session-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.session-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 15px;
}
.session-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 5px;
}
.session-status {
padding: 4px 12px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-active {
background-color: #c6f6d5;
color: #22543d;
}
.status-archived {
background-color: #e2e8f0;
color: #4a5568;
}
[data-theme="dark"] .status-active {
background-color: #22543d;
color: #c6f6d5;
}
[data-theme="dark"] .status-archived {
background-color: #4a5568;
color: #e2e8f0;
}
.session-meta {
display: flex;
gap: 15px;
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 15px;
}
.progress-bar {
width: 100%;
height: 8px;
background-color: var(--bg-primary);
border-radius: 4px;
overflow: hidden;
margin: 15px 0;
--background: 220 13% 10%;
--foreground: 0 0% 90%;
--card: 220 13% 14%;
--card-foreground: 0 0% 90%;
--border: 220 13% 20%;
--primary: 220 65% 55%;
--primary-foreground: 0 0% 100%;
--accent: 220 30% 20%;
--accent-foreground: 0 0% 90%;
--muted: 220 13% 18%;
--muted-foreground: 0 0% 55%;
--hover: 220 13% 22%;
--success: 142 71% 40%;
--success-light: 142 50% 20%;
--warning: 38 85% 45%;
--warning-light: 40 50% 20%;
}
/* Progress bar gradient */
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent-color), var(--success-color));
transition: width 0.3s;
background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--success)));
}
.tasks-list {
margin-top: 15px;
}
.task-item {
display: flex;
align-items: center;
padding: 10px;
margin-bottom: 8px;
background-color: var(--bg-primary);
border-radius: 6px;
border-left: 3px solid var(--border-color);
transition: all 0.2s;
}
.task-item:hover {
transform: translateX(4px);
}
.task-item.completed {
border-left-color: var(--success-color);
opacity: 0.8;
}
.task-item.in_progress {
border-left-color: var(--warning-color);
}
.task-item.pending {
border-left-color: var(--text-secondary);
}
.task-checkbox {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid var(--border-color);
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.task-item.completed .task-checkbox {
background-color: var(--success-color);
border-color: var(--success-color);
}
.task-item.completed .task-checkbox::after {
content: '✓';
color: white;
font-size: 0.8rem;
font-weight: bold;
}
.task-item.in_progress .task-checkbox {
border-color: var(--warning-color);
background-color: var(--warning-color);
}
.task-item.in_progress .task-checkbox::after {
content: '⟳';
color: white;
font-size: 0.9rem;
}
.task-title {
flex: 1;
font-size: 0.9rem;
}
.task-id {
font-size: 0.75rem;
color: var(--text-secondary);
font-family: monospace;
margin-left: 10px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-secondary);
}
.empty-state-icon {
font-size: 4rem;
margin-bottom: 20px;
opacity: 0.5;
}
.theme-toggle {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: var(--accent-color);
color: white;
border: none;
cursor: pointer;
font-size: 1.5rem;
box-shadow: var(--shadow-lg);
transition: all 0.3s;
z-index: 1000;
}
.theme-toggle:hover {
transform: scale(1.1);
}
@media (max-width: 768px) {
.sessions-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
h1 {
font-size: 1.5rem;
}
.header-controls {
flex-direction: column;
align-items: stretch;
}
.search-box {
width: 100%;
}
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
margin-left: 8px;
}
.badge-count {
background-color: var(--accent-color);
color: white;
}
.session-footer {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid var(--border-color);
font-size: 0.85rem;
color: var(--text-secondary);
}
/* Task checkbox pseudo-elements */
.task-checkbox.completed::after { content: '✓'; }
.task-checkbox.in_progress::after { content: '⟳'; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>🚀 Workflow Dashboard</h1>
<p style="color: var(--text-secondary);">Task Board - Active and Archived Sessions</p>
<body class="font-sans bg-background text-foreground leading-normal min-h-screen">
<div class="max-w-7xl mx-auto px-5 py-5">
<!-- Header -->
<header class="bg-card shadow rounded-lg p-5 mb-7 border border-border">
<h1 class="text-2xl font-bold text-primary mb-2">🚀 Workflow Dashboard</h1>
<p class="text-muted-foreground">Task Board - Active and Archived Sessions</p>
<div class="header-controls">
<div class="search-box">
<input type="text" id="searchInput" placeholder="🔍 Search tasks or sessions..." />
<div class="flex flex-wrap gap-4 items-center mt-4">
<!-- Search Box -->
<div class="flex-1 min-w-[250px] relative">
<input type="text" id="searchInput" placeholder="🔍 Search tasks or sessions..."
class="w-full px-4 py-2.5 border border-border rounded-lg bg-background text-foreground text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all" />
</div>
<div class="filter-group">
<button class="btn active" data-filter="all">All</button>
<button class="btn" data-filter="active">Active</button>
<button class="btn" data-filter="archived">Archived</button>
<!-- Filter Buttons -->
<div class="flex gap-2 flex-wrap">
<button class="btn px-5 py-2.5 border border-border rounded-lg text-sm font-medium bg-primary text-primary-foreground hover:opacity-90 transition-all" data-filter="all">All</button>
<button class="btn px-5 py-2.5 border border-border rounded-lg text-sm font-medium bg-card text-foreground hover:bg-hover transition-all" data-filter="active">Active</button>
<button class="btn px-5 py-2.5 border border-border rounded-lg text-sm font-medium bg-card text-foreground hover:bg-hover transition-all" data-filter="archived">Archived</button>
</div>
</div>
</header>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="totalSessions">0</div>
<div class="stat-label">Total Sessions</div>
<!-- Stats Grid -->
<div class="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-5 mb-7">
<div class="bg-card p-5 rounded-lg border border-border shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all duration-200">
<div class="text-3xl font-bold text-primary" id="totalSessions">0</div>
<div class="text-sm text-muted-foreground mt-1">Total Sessions</div>
</div>
<div class="stat-card">
<div class="stat-value" id="activeSessions">0</div>
<div class="stat-label">Active Sessions</div>
<div class="bg-card p-5 rounded-lg border border-border shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all duration-200">
<div class="text-3xl font-bold text-primary" id="activeSessionsCount">0</div>
<div class="text-sm text-muted-foreground mt-1">Active Sessions</div>
</div>
<div class="stat-card">
<div class="stat-value" id="totalTasks">0</div>
<div class="stat-label">Total Tasks</div>
<div class="bg-card p-5 rounded-lg border border-border shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all duration-200">
<div class="text-3xl font-bold text-primary" id="totalTasks">0</div>
<div class="text-sm text-muted-foreground mt-1">Total Tasks</div>
</div>
<div class="stat-card">
<div class="stat-value" id="completedTasks">0</div>
<div class="stat-label">Completed Tasks</div>
<div class="bg-card p-5 rounded-lg border border-border shadow-sm hover:shadow-md hover:-translate-y-0.5 transition-all duration-200">
<div class="text-3xl font-bold text-primary" id="completedTasks">0</div>
<div class="text-sm text-muted-foreground mt-1">Completed Tasks</div>
</div>
</div>
<div class="section" id="activeSectionContainer">
<div class="section-header">
<h2 class="section-title">📋 Active Sessions</h2>
<!-- Active Sessions Section -->
<div class="mb-10" id="activeSectionContainer">
<div class="flex justify-between items-center mb-5">
<h2 class="text-xl font-semibold text-foreground">📋 Active Sessions</h2>
</div>
<div class="sessions-grid" id="activeSessions"></div>
<div class="grid grid-cols-[repeat(auto-fill,minmax(350px,1fr))] gap-5" id="activeSessionsGrid"></div>
</div>
<div class="section" id="archivedSectionContainer">
<div class="section-header">
<h2 class="section-title">📦 Archived Sessions</h2>
<!-- Archived Sessions Section -->
<div class="mb-10" id="archivedSectionContainer">
<div class="flex justify-between items-center mb-5">
<h2 class="text-xl font-semibold text-foreground">📦 Archived Sessions</h2>
</div>
<div class="sessions-grid" id="archivedSessions"></div>
<div class="grid grid-cols-[repeat(auto-fill,minmax(350px,1fr))] gap-5" id="archivedSessionsGrid"></div>
</div>
</div>
<button class="theme-toggle" id="themeToggle">🌙</button>
<!-- Theme Toggle Button -->
<button class="fixed bottom-7 right-7 w-14 h-14 rounded-full bg-primary text-primary-foreground text-2xl shadow-lg hover:scale-110 transition-all duration-200 z-50" id="themeToggle">🌙</button>
<!-- Workflow data injected by dashboard-generator -->
<script id="workflow-data" type="application/json">{{WORKFLOW_DATA}}</script>
<script>
// Workflow data will be injected here
const workflowData = {{WORKFLOW_DATA}};
// Parse workflow data from JSON script tag, with fallback for direct file access
let workflowData;
try {
const dataScript = document.getElementById('workflow-data');
const rawData = dataScript ? dataScript.textContent.trim() : '';
// Check if placeholder was replaced (doesn't start with '{{')
if (rawData && !rawData.startsWith('{{')) {
workflowData = JSON.parse(rawData);
} else {
throw new Error('Data not injected');
}
} catch (e) {
console.warn('Workflow data not injected, using empty defaults');
workflowData = { activeSessions: [], archivedSessions: [] };
}
// Theme management
function initTheme() {
@@ -507,15 +228,15 @@
});
document.getElementById('totalSessions').textContent = stats.totalSessions;
document.getElementById('activeSessions').textContent = stats.activeSessions;
document.getElementById('activeSessionsCount').textContent = stats.activeSessions;
document.getElementById('totalTasks').textContent = stats.totalTasks;
document.getElementById('completedTasks').textContent = stats.completedTasks;
}
// Render session card
// Render session card with Tailwind classes
function createSessionCard(session, isActive) {
const card = document.createElement('div');
card.className = 'session-card';
card.className = 'bg-card rounded-lg border border-border shadow-sm p-5 transition-all duration-300 hover:-translate-y-1 hover:shadow-md';
card.dataset.sessionType = isActive ? 'active' : 'archived';
const completedTasks = isActive
@@ -527,49 +248,61 @@
let tasksHtml = '';
if (isActive && session.tasks.length > 0) {
tasksHtml = `
<div class="tasks-list">
${session.tasks.map(task => `
<div class="task-item ${task.status}">
<div class="task-checkbox"></div>
<div class="task-title">${task.title || 'Untitled Task'}</div>
<span class="task-id">${task.task_id || ''}</span>
</div>
`).join('')}
<div class="mt-4 space-y-2">
${session.tasks.map(task => {
const statusClasses = {
completed: 'border-l-success bg-success-light/30',
in_progress: 'border-l-warning bg-warning-light/30',
pending: 'border-l-muted-foreground bg-muted/30'
};
const checkboxClasses = {
completed: 'bg-success border-success text-white',
in_progress: 'border-warning text-warning',
pending: 'border-border'
};
return `
<div class="flex items-center gap-3 p-2.5 rounded border-l-[3px] ${statusClasses[task.status] || statusClasses.pending}">
<div class="task-checkbox w-5 h-5 rounded-full border-2 flex items-center justify-center text-xs font-bold flex-shrink-0 ${checkboxClasses[task.status] || checkboxClasses.pending} ${task.status}"></div>
<div class="flex-1 text-sm text-foreground">${task.title || 'Untitled Task'}</div>
<span class="text-xs font-mono text-muted-foreground">${task.task_id || ''}</span>
</div>
`;
}).join('')}
</div>
`;
}
const statusBadgeClass = isActive
? 'bg-success-light text-success'
: 'bg-hover text-muted-foreground';
card.innerHTML = `
<div class="session-header">
<div class="flex justify-between items-start mb-4">
<div>
<h3 class="session-title">${session.session_id || 'Unknown Session'}</h3>
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 5px;">
${session.project || ''}
</div>
<h3 class="text-lg font-semibold text-foreground">${session.session_id || 'Unknown Session'}</h3>
<div class="text-sm text-muted-foreground mt-1">${session.project || ''}</div>
</div>
<span class="session-status ${isActive ? 'status-active' : 'status-archived'}">
<span class="px-3 py-1 text-xs font-semibold uppercase rounded-full ${statusBadgeClass}">
${isActive ? 'Active' : 'Archived'}
</span>
</div>
<div class="session-meta">
<div class="flex gap-4 text-sm text-muted-foreground mb-3">
<span>📅 ${session.created_at || session.archived_at || 'N/A'}</span>
<span>📊 ${completedTasks}/${totalTasks} tasks</span>
</div>
${totalTasks > 0 ? `
<div class="progress-bar">
<div class="progress-fill" style="width: ${progress}%"></div>
</div>
<div style="text-align: center; font-size: 0.85rem; color: var(--text-secondary);">
${Math.round(progress)}% Complete
<div class="h-2 bg-hover rounded overflow-hidden my-4">
<div class="progress-fill h-full rounded transition-all duration-300" style="width: ${progress}%"></div>
</div>
<div class="text-center text-sm text-muted-foreground">${Math.round(progress)}% Complete</div>
` : ''}
${tasksHtml}
${!isActive && session.archive_path ? `
<div class="session-footer">
<div class="mt-4 pt-4 border-t border-border text-sm text-muted-foreground">
📁 Archive: ${session.archive_path}
</div>
` : ''}
@@ -580,8 +313,8 @@
// Render all sessions
function renderSessions(filter = 'all') {
const activeContainer = document.getElementById('activeSessions');
const archivedContainer = document.getElementById('archivedSessions');
const activeContainer = document.getElementById('activeSessionsGrid');
const archivedContainer = document.getElementById('archivedSessionsGrid');
activeContainer.innerHTML = '';
archivedContainer.innerHTML = '';
@@ -589,8 +322,8 @@
if (filter === 'all' || filter === 'active') {
if (workflowData.activeSessions.length === 0) {
activeContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📭</div>
<div class="text-center py-16 text-muted-foreground col-span-full">
<div class="text-5xl mb-4 opacity-50">📭</div>
<p>No active sessions</p>
</div>
`;
@@ -604,8 +337,8 @@
if (filter === 'all' || filter === 'archived') {
if (workflowData.archivedSessions.length === 0) {
archivedContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📦</div>
<div class="text-center py-16 text-muted-foreground col-span-full">
<div class="text-5xl mb-4 opacity-50">📦</div>
<p>No archived sessions</p>
</div>
`;
@@ -628,7 +361,7 @@
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
const cards = document.querySelectorAll('.session-card');
const cards = document.querySelectorAll('[data-session-type]');
cards.forEach(card => {
const text = card.textContent.toLowerCase();
@@ -642,8 +375,12 @@
const filterButtons = document.querySelectorAll('[data-filter]');
filterButtons.forEach(btn => {
btn.addEventListener('click', () => {
filterButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
filterButtons.forEach(b => {
b.classList.remove('bg-primary', 'text-primary-foreground');
b.classList.add('bg-card', 'text-foreground');
});
btn.classList.remove('bg-card', 'text-foreground');
btn.classList.add('bg-primary', 'text-primary-foreground');
renderSessions(btn.dataset.filter);
});
});
@@ -661,4 +398,4 @@
});
</script>
</body>
</html>
</html>

View File

@@ -0,0 +1,664 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Workflow Dashboard - Task Board</title>
<style>
:root {
--bg-primary: #f5f7fa;
--bg-secondary: #ffffff;
--bg-card: #ffffff;
--text-primary: #1a202c;
--text-secondary: #718096;
--border-color: #e2e8f0;
--accent-color: #4299e1;
--success-color: #48bb78;
--warning-color: #ed8936;
--danger-color: #f56565;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
[data-theme="dark"] {
--bg-primary: #1a202c;
--bg-secondary: #2d3748;
--bg-card: #2d3748;
--text-primary: #f7fafc;
--text-secondary: #a0aec0;
--border-color: #4a5568;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
transition: background-color 0.3s, color 0.3s;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: var(--bg-secondary);
box-shadow: var(--shadow);
padding: 20px;
margin-bottom: 30px;
border-radius: 8px;
}
h1 {
font-size: 2rem;
margin-bottom: 10px;
color: var(--accent-color);
}
.header-controls {
display: flex;
gap: 15px;
flex-wrap: wrap;
align-items: center;
margin-top: 15px;
}
.search-box {
flex: 1;
min-width: 250px;
position: relative;
}
.search-box input {
width: 100%;
padding: 10px 15px;
border: 1px solid var(--border-color);
border-radius: 6px;
background-color: var(--bg-primary);
color: var(--text-primary);
font-size: 0.95rem;
}
.filter-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.2s;
background-color: var(--bg-card);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow);
}
.btn.active {
background-color: var(--accent-color);
color: white;
border-color: var(--accent-color);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background-color: var(--bg-card);
padding: 20px;
border-radius: 8px;
box-shadow: var(--shadow);
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: var(--accent-color);
}
.stat-label {
color: var(--text-secondary);
font-size: 0.9rem;
margin-top: 5px;
}
.section {
margin-bottom: 40px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.section-title {
font-size: 1.5rem;
font-weight: 600;
}
.sessions-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
}
.session-card {
background-color: var(--bg-card);
border-radius: 8px;
box-shadow: var(--shadow);
padding: 20px;
transition: all 0.3s;
}
.session-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.session-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 15px;
}
.session-title {
font-size: 1.2rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 5px;
}
.session-status {
padding: 4px 12px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.status-active {
background-color: #c6f6d5;
color: #22543d;
}
.status-archived {
background-color: #e2e8f0;
color: #4a5568;
}
[data-theme="dark"] .status-active {
background-color: #22543d;
color: #c6f6d5;
}
[data-theme="dark"] .status-archived {
background-color: #4a5568;
color: #e2e8f0;
}
.session-meta {
display: flex;
gap: 15px;
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 15px;
}
.progress-bar {
width: 100%;
height: 8px;
background-color: var(--bg-primary);
border-radius: 4px;
overflow: hidden;
margin: 15px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--accent-color), var(--success-color));
transition: width 0.3s;
}
.tasks-list {
margin-top: 15px;
}
.task-item {
display: flex;
align-items: center;
padding: 10px;
margin-bottom: 8px;
background-color: var(--bg-primary);
border-radius: 6px;
border-left: 3px solid var(--border-color);
transition: all 0.2s;
}
.task-item:hover {
transform: translateX(4px);
}
.task-item.completed {
border-left-color: var(--success-color);
opacity: 0.8;
}
.task-item.in_progress {
border-left-color: var(--warning-color);
}
.task-item.pending {
border-left-color: var(--text-secondary);
}
.task-checkbox {
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid var(--border-color);
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.task-item.completed .task-checkbox {
background-color: var(--success-color);
border-color: var(--success-color);
}
.task-item.completed .task-checkbox::after {
content: '✓';
color: white;
font-size: 0.8rem;
font-weight: bold;
}
.task-item.in_progress .task-checkbox {
border-color: var(--warning-color);
background-color: var(--warning-color);
}
.task-item.in_progress .task-checkbox::after {
content: '⟳';
color: white;
font-size: 0.9rem;
}
.task-title {
flex: 1;
font-size: 0.9rem;
}
.task-id {
font-size: 0.75rem;
color: var(--text-secondary);
font-family: monospace;
margin-left: 10px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-secondary);
}
.empty-state-icon {
font-size: 4rem;
margin-bottom: 20px;
opacity: 0.5;
}
.theme-toggle {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: var(--accent-color);
color: white;
border: none;
cursor: pointer;
font-size: 1.5rem;
box-shadow: var(--shadow-lg);
transition: all 0.3s;
z-index: 1000;
}
.theme-toggle:hover {
transform: scale(1.1);
}
@media (max-width: 768px) {
.sessions-grid {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
h1 {
font-size: 1.5rem;
}
.header-controls {
flex-direction: column;
align-items: stretch;
}
.search-box {
width: 100%;
}
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
margin-left: 8px;
}
.badge-count {
background-color: var(--accent-color);
color: white;
}
.session-footer {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid var(--border-color);
font-size: 0.85rem;
color: var(--text-secondary);
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🚀 Workflow Dashboard</h1>
<p style="color: var(--text-secondary);">Task Board - Active and Archived Sessions</p>
<div class="header-controls">
<div class="search-box">
<input type="text" id="searchInput" placeholder="🔍 Search tasks or sessions..." />
</div>
<div class="filter-group">
<button class="btn active" data-filter="all">All</button>
<button class="btn" data-filter="active">Active</button>
<button class="btn" data-filter="archived">Archived</button>
</div>
</div>
</header>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="totalSessions">0</div>
<div class="stat-label">Total Sessions</div>
</div>
<div class="stat-card">
<div class="stat-value" id="activeSessions">0</div>
<div class="stat-label">Active Sessions</div>
</div>
<div class="stat-card">
<div class="stat-value" id="totalTasks">0</div>
<div class="stat-label">Total Tasks</div>
</div>
<div class="stat-card">
<div class="stat-value" id="completedTasks">0</div>
<div class="stat-label">Completed Tasks</div>
</div>
</div>
<div class="section" id="activeSectionContainer">
<div class="section-header">
<h2 class="section-title">📋 Active Sessions</h2>
</div>
<div class="sessions-grid" id="activeSessions"></div>
</div>
<div class="section" id="archivedSectionContainer">
<div class="section-header">
<h2 class="section-title">📦 Archived Sessions</h2>
</div>
<div class="sessions-grid" id="archivedSessions"></div>
</div>
</div>
<button class="theme-toggle" id="themeToggle">🌙</button>
<script>
// Workflow data will be injected here
const workflowData = {{WORKFLOW_DATA}};
// Theme management
function initTheme() {
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
}
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeIcon(newTheme);
}
function updateThemeIcon(theme) {
document.getElementById('themeToggle').textContent = theme === 'dark' ? '☀️' : '🌙';
}
// Statistics calculation
function updateStatistics() {
const stats = {
totalSessions: workflowData.activeSessions.length + workflowData.archivedSessions.length,
activeSessions: workflowData.activeSessions.length,
totalTasks: 0,
completedTasks: 0
};
workflowData.activeSessions.forEach(session => {
stats.totalTasks += session.tasks.length;
stats.completedTasks += session.tasks.filter(t => t.status === 'completed').length;
});
workflowData.archivedSessions.forEach(session => {
stats.totalTasks += session.taskCount || 0;
stats.completedTasks += session.taskCount || 0;
});
document.getElementById('totalSessions').textContent = stats.totalSessions;
document.getElementById('activeSessions').textContent = stats.activeSessions;
document.getElementById('totalTasks').textContent = stats.totalTasks;
document.getElementById('completedTasks').textContent = stats.completedTasks;
}
// Render session card
function createSessionCard(session, isActive) {
const card = document.createElement('div');
card.className = 'session-card';
card.dataset.sessionType = isActive ? 'active' : 'archived';
const completedTasks = isActive
? session.tasks.filter(t => t.status === 'completed').length
: (session.taskCount || 0);
const totalTasks = isActive ? session.tasks.length : (session.taskCount || 0);
const progress = totalTasks > 0 ? (completedTasks / totalTasks * 100) : 0;
let tasksHtml = '';
if (isActive && session.tasks.length > 0) {
tasksHtml = `
<div class="tasks-list">
${session.tasks.map(task => `
<div class="task-item ${task.status}">
<div class="task-checkbox"></div>
<div class="task-title">${task.title || 'Untitled Task'}</div>
<span class="task-id">${task.task_id || ''}</span>
</div>
`).join('')}
</div>
`;
}
card.innerHTML = `
<div class="session-header">
<div>
<h3 class="session-title">${session.session_id || 'Unknown Session'}</h3>
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-top: 5px;">
${session.project || ''}
</div>
</div>
<span class="session-status ${isActive ? 'status-active' : 'status-archived'}">
${isActive ? 'Active' : 'Archived'}
</span>
</div>
<div class="session-meta">
<span>📅 ${session.created_at || session.archived_at || 'N/A'}</span>
<span>📊 ${completedTasks}/${totalTasks} tasks</span>
</div>
${totalTasks > 0 ? `
<div class="progress-bar">
<div class="progress-fill" style="width: ${progress}%"></div>
</div>
<div style="text-align: center; font-size: 0.85rem; color: var(--text-secondary);">
${Math.round(progress)}% Complete
</div>
` : ''}
${tasksHtml}
${!isActive && session.archive_path ? `
<div class="session-footer">
📁 Archive: ${session.archive_path}
</div>
` : ''}
`;
return card;
}
// Render all sessions
function renderSessions(filter = 'all') {
const activeContainer = document.getElementById('activeSessions');
const archivedContainer = document.getElementById('archivedSessions');
activeContainer.innerHTML = '';
archivedContainer.innerHTML = '';
if (filter === 'all' || filter === 'active') {
if (workflowData.activeSessions.length === 0) {
activeContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📭</div>
<p>No active sessions</p>
</div>
`;
} else {
workflowData.activeSessions.forEach(session => {
activeContainer.appendChild(createSessionCard(session, true));
});
}
}
if (filter === 'all' || filter === 'archived') {
if (workflowData.archivedSessions.length === 0) {
archivedContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📦</div>
<p>No archived sessions</p>
</div>
`;
} else {
workflowData.archivedSessions.forEach(session => {
archivedContainer.appendChild(createSessionCard(session, false));
});
}
}
// Show/hide sections
document.getElementById('activeSectionContainer').style.display =
(filter === 'all' || filter === 'active') ? 'block' : 'none';
document.getElementById('archivedSectionContainer').style.display =
(filter === 'all' || filter === 'archived') ? 'block' : 'none';
}
// Search functionality
function setupSearch() {
const searchInput = document.getElementById('searchInput');
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
const cards = document.querySelectorAll('.session-card');
cards.forEach(card => {
const text = card.textContent.toLowerCase();
card.style.display = text.includes(query) ? 'block' : 'none';
});
});
}
// Filter functionality
function setupFilters() {
const filterButtons = document.querySelectorAll('[data-filter]');
filterButtons.forEach(btn => {
btn.addEventListener('click', () => {
filterButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderSessions(btn.dataset.filter);
});
});
}
// Initialize
document.addEventListener('DOMContentLoaded', () => {
initTheme();
updateStatistics();
renderSessions();
setupSearch();
setupFilters();
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
});
</script>
</body>
</html>