+ {/* Timeline connector + dot */}
+
+
+ {!isLast && (
+
+ )}
+
{/* Content */}
-
+
-
+
{formatTimestamp(evt.timestamp)}
- {evt.appName}
+ {evt.appName}
{detail && (
-
{detail}
+
{detail}
)}
@@ -124,7 +130,7 @@ export default function ActivityFeed({ minutes, taskId }: ActivityFeedProps) {
{hasMore && (
diff --git a/TaskTracker.Web/src/components/analytics/CategoryBreakdown.tsx b/TaskTracker.Web/src/components/analytics/CategoryBreakdown.tsx
index 50fcf52..9c40f22 100644
--- a/TaskTracker.Web/src/components/analytics/CategoryBreakdown.tsx
+++ b/TaskTracker.Web/src/components/analytics/CategoryBreakdown.tsx
@@ -44,7 +44,7 @@ export default function CategoryBreakdown({ minutes: _minutes, taskId: _taskId }
if (isLoading) {
return (
-
+
Loading category breakdown...
)
@@ -52,7 +52,7 @@ export default function CategoryBreakdown({ minutes: _minutes, taskId: _taskId }
if (categories.length === 0) {
return (
-
+
No category data available.
)
@@ -88,14 +88,14 @@ export default function CategoryBreakdown({ minutes: _minutes, taskId: _taskId }
return (
-
{d.name}
-
+
{d.name}
+
{d.count} events ({d.percentage}%)
@@ -119,13 +119,13 @@ export default function CategoryBreakdown({ minutes: _minutes, taskId: _taskId }
{/* Name + bar + stats */}
- {cat.name}
-
+ {cat.name}
+
{cat.count} ({cat.percentage}%)
{/* Progress bar */}
-
+
+
Loading timeline...
)
@@ -144,7 +144,7 @@ export default function Timeline({ minutes, taskId }: TimelineProps) {
if (buckets.length === 0) {
return (
-
+
No activity data for this time range.
)
@@ -156,12 +156,12 @@ export default function Timeline({ minutes, taskId }: TimelineProps) {
- {d.timeRange}
- {d.appName}
+ {d.timeRange}
+ {d.appName}
{d.count} events
diff --git a/TaskTracker.Web/src/pages/Analytics.tsx b/TaskTracker.Web/src/pages/Analytics.tsx
index 204f80d..2458dc8 100644
--- a/TaskTracker.Web/src/pages/Analytics.tsx
+++ b/TaskTracker.Web/src/pages/Analytics.tsx
@@ -19,14 +19,14 @@ export default function Analytics() {
{/* Header + Filters */}
-
Analytics
+
Analytics
{/* Time range dropdown */}
+ {/* Stat cards */}
+
+
+
Open Tasks
+
+ {tasks?.filter(t => t.status !== 'Completed' && t.status !== 'Abandoned').length ?? 0}
+
+
+
+
Active Time
+
+ {(() => {
+ const totalMins = tasks?.reduce((acc, t) => {
+ if (!t.startedAt) return acc
+ const start = new Date(t.startedAt).getTime()
+ const end = t.completedAt ? new Date(t.completedAt).getTime() : (t.status === 'Active' ? Date.now() : start)
+ return acc + (end - start) / 60000
+ }, 0) ?? 0
+ const hours = Math.floor(totalMins / 60)
+ const mins = Math.floor(totalMins % 60)
+ return hours > 0 ? `${hours}h ${mins}m` : `${mins}m`
+ })()}
+
+
+
+
Top Category
+
+ {(() => {
+ const counts: Record = {}
+ tasks?.forEach(t => { counts[t.category ?? 'Unknown'] = (counts[t.category ?? 'Unknown'] || 0) + 1 })
+ const top = Object.entries(counts).sort(([,a], [,b]) => b - a)[0]
+ return top ? top[0] : '\u2014'
+ })()}
+
+
+
+
{/* Timeline */}
-
+
Activity Timeline
-
+
{/* Category Breakdown */}