feat(dashboard): add refresh indicator during data fetch

- Show spinning RefreshCw icon and 'Refreshing...' text during fetch
- Expose isFetching state from useMetrics hook
- Dashboard already has all required metrics features:
  - Run count from real telemetry data
  - Success rate with color coding (green/yellow/red)
  - Token usage with input/output/cache breakdown
  - Duration with avg/p50/p95 percentiles
  - Trend indicators (↑/↓) comparing to previous period
  - Auto-refresh every 30 seconds
  - Graceful empty state when no data

Closes US-1405
This commit is contained in:
Brad Groux 2026-01-28 08:13:55 -06:00
parent fdf5ff187d
commit b53ef09dcd
5 changed files with 128 additions and 4 deletions

View file

@ -1044,3 +1044,66 @@
{"type":"task.archived","taskId":"task_20260128_o0FPtg","project":"veritas-kanban","status":"done","id":"evt_0gdktmGAqnRA","timestamp":"2026-01-28T14:12:01.970Z"}
{"type":"task.archived","taskId":"task_20260128_q38wnR","project":"veritas-kanban","status":"done","id":"evt_B3rKqlEaywqP","timestamp":"2026-01-28T14:12:01.971Z"}
{"type":"task.archived","taskId":"task_20260128_tD7fef","project":"veritas-kanban","status":"done","id":"evt_b2Kt6rRrap0P","timestamp":"2026-01-28T14:12:01.972Z"}
{"type":"task.status_changed","taskId":"task_20260128_V0MVwS","project":"veritas-kanban","status":"done","previousStatus":"in-progress","id":"evt_EIdDEc4fzlA9","timestamp":"2026-01-28T14:12:56.406Z"}
{"type":"task.status_changed","taskId":"task_20260126_legacy1","status":"blocked","previousStatus":"review","id":"evt_aJKeQEJzFVlh","timestamp":"2026-01-28T14:13:49.706Z"}
{"type":"task.created","taskId":"task_20260128_qeDVe7","status":"todo","id":"evt_qF3rAJT_JkA-","timestamp":"2026-01-28T14:13:49.711Z"}
{"type":"task.created","taskId":"task_20260128_JuSpOm","status":"todo","id":"evt_y_URQGQeqFd7","timestamp":"2026-01-28T14:13:49.716Z"}
{"type":"task.status_changed","taskId":"task_20260128_JuSpOm","status":"in-progress","previousStatus":"todo","id":"evt_0GdekA5NSe9b","timestamp":"2026-01-28T14:13:49.719Z"}
{"type":"task.created","taskId":"task_20260128_664iza","status":"todo","id":"evt_yCUA_i9DTyFb","timestamp":"2026-01-28T14:13:49.720Z"}
{"type":"task.status_changed","taskId":"task_20260128_664iza","status":"blocked","previousStatus":"todo","id":"evt_yGUFtpfZDadq","timestamp":"2026-01-28T14:13:49.723Z"}
{"type":"task.created","taskId":"task_20260128_wLa9-l","status":"todo","id":"evt_BUzHobjagh0M","timestamp":"2026-01-28T14:13:49.724Z"}
{"type":"task.status_changed","taskId":"task_20260128_wLa9-l","status":"done","previousStatus":"todo","id":"evt_yc0d-aT8hYPF","timestamp":"2026-01-28T14:13:49.727Z"}
{"type":"task.status_changed","taskId":"task_20260126_idem1","status":"blocked","previousStatus":"review","id":"evt_-RHZi0G2MeXn","timestamp":"2026-01-28T14:13:49.740Z"}
{"type":"task.created","taskId":"task_20260128_56EzyF","project":"my-project","status":"todo","id":"evt_Y3SKILqPxO2O","timestamp":"2026-01-28T14:13:49.741Z"}
{"type":"task.created","taskId":"task_20260128_EW5A0c","status":"todo","id":"evt_EajwoH4VXoqt","timestamp":"2026-01-28T14:13:49.744Z"}
{"type":"task.created","taskId":"task_20260128_t5JkOe","status":"todo","id":"evt_WCfjJrq-Injc","timestamp":"2026-01-28T14:13:49.746Z"}
{"type":"task.created","taskId":"task_20260128_i00QLj","status":"todo","id":"evt_y1G8ehDhh3Wm","timestamp":"2026-01-28T14:13:49.749Z"}
{"type":"task.status_changed","taskId":"task_20260126_multi1","status":"blocked","previousStatus":"review","id":"evt_2T2SN1fHJHEH","timestamp":"2026-01-28T14:13:49.751Z"}
{"type":"task.status_changed","taskId":"task_20260126_multi2","status":"blocked","previousStatus":"review","id":"evt_pwMXj_ggyr2m","timestamp":"2026-01-28T14:13:49.753Z"}
{"type":"task.status_changed","taskId":"task_20260128_i00QLj","status":"in-progress","previousStatus":"todo","id":"evt_lhxwY8L0vV1R","timestamp":"2026-01-28T14:13:49.761Z"}
{"type":"task.created","taskId":"task_20260128_aeg-ro","status":"todo","id":"evt_PMIUKN5xoS0h","timestamp":"2026-01-28T14:13:49.767Z"}
{"type":"task.created","taskId":"task_20260128_guLYDE","status":"todo","id":"evt_pJh1_QEZXIoZ","timestamp":"2026-01-28T14:13:49.775Z"}
{"type":"task.restored","taskId":"task_20260126_arch1","status":"done","id":"evt_NJPC4BbV5KQt","timestamp":"2026-01-28T14:13:49.783Z"}
{"type":"task.status_changed","taskId":"task_20260126_arch1","status":"blocked","previousStatus":"done","id":"evt_njisU91EW8L9","timestamp":"2026-01-28T14:13:49.784Z"}
{"type":"task.archived","taskId":"task_20260126_arch1","status":"blocked","id":"evt_NRcgw6pR_dhS","timestamp":"2026-01-28T14:13:49.789Z"}
{"type":"task.created","taskId":"task_20260128_DHmiw3","status":"todo","id":"evt_WUAkr3Uao_jq","timestamp":"2026-01-28T14:13:49.795Z"}
{"type":"task.created","taskId":"task_20260128_8ZiG0y","status":"todo","id":"evt_3WUhEVT9pFke","timestamp":"2026-01-28T14:13:49.803Z"}
{"type":"task.archived","taskId":"task_20260128_8ZiG0y","status":"todo","id":"evt_sbqAjp377WEw","timestamp":"2026-01-28T14:13:49.806Z"}
{"type":"task.created","taskId":"task_20260128_Dxfb66","status":"todo","id":"evt_YbnRJ-Oah-C1","timestamp":"2026-01-28T14:13:49.807Z"}
{"type":"task.created","taskId":"task_20260128_j4v-ml","status":"todo","id":"evt_EwxCDT_8Epgc","timestamp":"2026-01-28T14:13:49.810Z"}
{"type":"task.created","taskId":"task_20260128_FyeeJm","status":"todo","id":"evt_xgehu5XzNRZ4","timestamp":"2026-01-28T14:13:49.832Z"}
{"type":"task.created","taskId":"task_20260128_feXRCT","status":"todo","id":"evt_ZsnxO4ZlBVZU","timestamp":"2026-01-28T14:13:49.833Z"}
{"type":"task.created","taskId":"task_20260128_O8LwAG","status":"todo","id":"evt_BBALxBLr05JU","timestamp":"2026-01-28T14:13:49.843Z"}
{"type":"task.created","taskId":"task_20260128_DhgY5l","status":"todo","id":"evt_EWNd5I4cKS6Z","timestamp":"2026-01-28T14:13:49.843Z"}
{"type":"task.created","taskId":"task_20260128_PVtzVP","project":"test-project","status":"todo","id":"evt_d4aHuProMU3Y","timestamp":"2026-01-28T14:13:49.850Z"}
{"type":"task.created","taskId":"task_20260128_QXbYX7","status":"todo","id":"evt_ajLMYiNp5WUn","timestamp":"2026-01-28T14:13:49.856Z"}
{"type":"task.created","taskId":"task_20260128_6TzEJP","status":"todo","id":"evt_7-HJz_BBigJp","timestamp":"2026-01-28T14:13:49.859Z"}
{"type":"task.created","taskId":"task_20260128_pVir6P","status":"todo","id":"evt_jomFWF36skWJ","timestamp":"2026-01-28T14:13:49.869Z"}
{"type":"task.created","taskId":"task_20260128_BhxcoA","status":"todo","id":"evt_Ws3O_bsSi9jo","timestamp":"2026-01-28T14:13:49.870Z"}
{"type":"task.status_changed","taskId":"task_20260128_pVir6P","status":"in-progress","previousStatus":"todo","id":"evt_eQ-I8ghnB6NB","timestamp":"2026-01-28T14:13:49.872Z"}
{"type":"task.created","taskId":"task_20260128_7Hlf3i","status":"todo","id":"evt_VUEzjHZKI9dn","timestamp":"2026-01-28T14:13:49.875Z"}
{"type":"task.created","taskId":"task_20260128_vEuwon","status":"todo","id":"evt_9WQ8ClfOQjsS","timestamp":"2026-01-28T14:13:49.877Z"}
{"type":"task.created","taskId":"task_20260128_TlJaN9","status":"todo","id":"evt_iHDk5pd8-vAs","timestamp":"2026-01-28T14:13:49.880Z"}
{"type":"task.created","taskId":"task_20260128_OVULhb","status":"todo","id":"evt_0wopJ2nDMHgl","timestamp":"2026-01-28T14:13:49.880Z"}
{"type":"task.created","taskId":"task_20260128_LNFgfG","status":"todo","id":"evt_4SPvlaIc5oNQ","timestamp":"2026-01-28T14:13:49.886Z"}
{"type":"task.created","taskId":"task_20260128_oxhHCK","status":"todo","id":"evt_ZL_aKJeDzk36","timestamp":"2026-01-28T14:13:49.888Z"}
{"type":"task.created","taskId":"task_20260128_JGKvCV","status":"todo","id":"evt_uYecKQyBVX15","timestamp":"2026-01-28T14:13:49.889Z"}
{"type":"task.created","taskId":"task_20260128_b38X4p","status":"todo","id":"evt_pNUDid_I7Q-G","timestamp":"2026-01-28T14:13:49.890Z"}
{"type":"task.created","taskId":"task_20260128_1Nh91U","status":"todo","id":"evt_71eeAPanN2ZK","timestamp":"2026-01-28T14:13:49.894Z"}
{"type":"task.created","taskId":"task_20260128_qowqT9","status":"todo","id":"evt_0qHMP5wYHu4O","timestamp":"2026-01-28T14:13:49.898Z"}
{"type":"task.created","taskId":"task_20260128_FxN6To","status":"todo","id":"evt_1v8_5Vnb6gez","timestamp":"2026-01-28T14:13:49.902Z"}
{"type":"task.created","taskId":"task_20260128_oMsuBK","status":"todo","id":"evt_1ujGfMgnI63A","timestamp":"2026-01-28T14:13:49.904Z"}
{"type":"task.created","taskId":"task_20260128_GCHHR-","status":"todo","id":"evt_IYIOhiVcviqd","timestamp":"2026-01-28T14:13:49.904Z"}
{"type":"task.created","taskId":"task_20260128_WsJPVK","status":"todo","id":"evt_libonFSyzB2L","timestamp":"2026-01-28T14:13:49.905Z"}
{"type":"task.created","taskId":"task_20260128_D_pyB9","status":"todo","id":"evt_p01Mmh9rYjLf","timestamp":"2026-01-28T14:13:49.906Z"}
{"type":"task.created","taskId":"task_20260128_R0kUOu","status":"todo","id":"evt_Jj8QuYrZJxs0","timestamp":"2026-01-28T14:13:49.909Z"}
{"type":"task.created","taskId":"task_20260128_GRZ3Ky","status":"todo","id":"evt_zxeZSRY_NoBq","timestamp":"2026-01-28T14:13:49.910Z"}
{"type":"task.created","taskId":"task_20260128_cxwVaV","status":"todo","id":"evt_ubDlTAHwaUhB","timestamp":"2026-01-28T14:13:49.916Z"}
{"type":"task.created","taskId":"task_20260128_dSHkGk","status":"todo","id":"evt_REPqQbIgsU5s","timestamp":"2026-01-28T14:13:49.921Z"}
{"type":"task.created","taskId":"task_20260128_Jt3pVG","status":"todo","id":"evt_FJCpmdXPSyZ2","timestamp":"2026-01-28T14:13:49.927Z"}
{"type":"task.created","taskId":"task_20260128_g7oMUz","status":"todo","id":"evt_-tgPW_4eIqeK","timestamp":"2026-01-28T14:13:49.931Z"}
{"type":"task.created","taskId":"task_20260128_1AL-Ps","status":"todo","id":"evt_N9R0LVDJLtC1","timestamp":"2026-01-28T14:13:49.936Z"}
{"type":"task.created","taskId":"task_20260128_WehfEe","project":"project-a","status":"todo","id":"evt_BCp5yYwOXlU9","timestamp":"2026-01-28T14:13:49.936Z"}
{"type":"task.created","taskId":"task_20260128_b2CW4g","project":"project-a","status":"todo","id":"evt_-g7kMhjBnCYG","timestamp":"2026-01-28T14:13:49.936Z"}
{"type":"task.created","taskId":"task_20260128_NEm1K_","project":"project-b","status":"todo","id":"evt_aCXwbrsfHVkW","timestamp":"2026-01-28T14:13:49.937Z"}
{"type":"task.created","taskId":"task_20260128_eCyXeF","status":"todo","id":"evt_qx8JflCKJ_1V","timestamp":"2026-01-28T14:13:49.944Z"}

View file

@ -1,4 +1,44 @@
[
{
"id": "activity_1769609585007_vp6ouz8mc",
"type": "comment_added",
"taskId": "task_20260128_efQBVi",
"taskTitle": "US-1011: Cost budget tracking",
"details": {
"author": "Veritas",
"preview": "Verified complete implementation: Budget settings ..."
},
"timestamp": "2026-01-28T14:13:05.007Z"
},
{
"id": "activity_1769609584757_n6s7jq73m",
"type": "task_updated",
"taskId": "task_20260128_efQBVi",
"taskTitle": "US-1011: Cost budget tracking",
"timestamp": "2026-01-28T14:13:04.757Z"
},
{
"id": "activity_1769609584571_l6zlkl6ou",
"type": "comment_added",
"taskId": "task_20260128_V0MVwS",
"taskTitle": "US-1405: Dashboard metrics cards refresh",
"details": {
"author": "Veritas",
"preview": "Dashboard metrics cards now display real telemetry..."
},
"timestamp": "2026-01-28T14:13:04.571Z"
},
{
"id": "activity_1769609576406_awrw3g8pe",
"type": "status_changed",
"taskId": "task_20260128_V0MVwS",
"taskTitle": "US-1405: Dashboard metrics cards refresh",
"details": {
"from": "in-progress",
"status": "done"
},
"timestamp": "2026-01-28T14:12:56.406Z"
},
{
"id": "activity_1769609521972_4we6i0l5b",
"type": "sprint_archived",

View file

@ -1,4 +1,12 @@
[
{
"id": "status_1769609601418_kzwyjd0u0",
"timestamp": "2026-01-28T14:13:21.418Z",
"previousStatus": "sub-agent",
"newStatus": "idle",
"subAgentCount": 1,
"durationMs": 98854
},
{
"id": "status_1769609502564_sefpbuxpw",
"timestamp": "2026-01-28T14:11:42.564Z",

View file

@ -188,7 +188,7 @@ export function Dashboard() {
const [project, setProject] = useState<string | undefined>(undefined);
const [drillDown, setDrillDown] = useState<DrillDownType>(null);
const { data: metrics, isLoading, error, dataUpdatedAt } = useMetrics(period, project);
const { data: metrics, isLoading, isFetching, error, dataUpdatedAt } = useMetrics(period, project);
const { data: tasks } = useTasks();
// Get unique projects from tasks
@ -257,8 +257,8 @@ export function Dashboard() {
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<RefreshCw className="h-3 w-3" />
Updated {new Date(dataUpdatedAt).toLocaleTimeString()}
<RefreshCw className={cn('h-3 w-3', isFetching && 'animate-spin')} />
{isFetching ? 'Refreshing...' : `Updated ${new Date(dataUpdatedAt).toLocaleTimeString()}`}
</div>
</div>

View file

@ -1,5 +1,5 @@
import { useState } from 'react';
import { Activity, Trash2, RefreshCw } from 'lucide-react';
import { Activity, Trash2, RefreshCw, Clock, Coffee, ArrowRight, Zap } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import {
@ -15,7 +15,20 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '@/components/ui/tabs';
import { useActivities, useClearActivities, type Activity as ActivityItem, type ActivityType } from '@/hooks/useActivity';
import {
useDailySummary,
useStatusHistory,
formatDurationMs,
getStatusColor,
type StatusHistoryEntry,
} from '@/hooks/useStatusHistory';
import { cn } from '@/lib/utils';
interface ActivitySidebarProps {