diff --git a/TaskTracker.Web/src/components/analytics/ActivityFeed.tsx b/TaskTracker.Web/src/components/analytics/ActivityFeed.tsx index fe64cf5..751a2a1 100644 --- a/TaskTracker.Web/src/components/analytics/ActivityFeed.tsx +++ b/TaskTracker.Web/src/components/analytics/ActivityFeed.tsx @@ -74,7 +74,7 @@ export default function ActivityFeed({ minutes, taskId }: ActivityFeedProps) { if (eventsLoading || mappingsLoading) { return ( -
+
Loading activity...
) @@ -82,7 +82,7 @@ export default function ActivityFeed({ minutes, taskId }: ActivityFeedProps) { if (sortedEvents.length === 0) { return ( -
+
No activity events for this time range.
) @@ -90,30 +90,36 @@ export default function ActivityFeed({ minutes, taskId }: ActivityFeedProps) { return (
-
- {visibleEvents.map((evt) => { +
+ {visibleEvents.map((evt, idx) => { const category = mappings ? resolveCategory(evt.appName, mappings) : 'Unknown' const color = CATEGORY_COLORS[category] ?? CATEGORY_COLORS['Unknown'] const detail = evt.url || evt.windowTitle || '' + const isLast = idx === visibleEvents.length - 1 return ( -
- {/* Category dot */} - +
+ {/* 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 */} setTaskId(e.target.value ? Number(e.target.value) : undefined)} - className="bg-[#1e293b] text-white text-sm rounded-lg border border-white/10 px-3 py-1.5 focus:outline-none focus:border-indigo-500 transition-colors appearance-none cursor-pointer max-w-[200px]" + className="bg-[var(--color-surface)] text-[var(--color-text-primary)] text-sm rounded-lg border border-[var(--color-border)] px-3 py-1.5 focus:outline-none focus:border-[var(--color-accent)] transition-colors appearance-none cursor-pointer max-w-[200px]" > {tasks?.map((t) => ( @@ -51,32 +51,69 @@ export default function Analytics() {
+ {/* 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 */}
-

+

Category Breakdown

-
+
{/* Activity Feed */}
-

+

Recent Activity

-
+