Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | 37x 37x 144x 37x 37x 148x 148x 148x 148x 148x 148x 148x 148x 148x 212x 212x 212x 212x 640x 640x 212x 148x 148x | import { useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import { Activity, Calendar, AlertTriangle, ArrowDownToLine, ArrowUpFromLine } from 'lucide-react'
// depcruiser:ignore l3-not-lower — Custom view: dashboard stats (wireframes/dashboard-page.md)
import { useEntityList } from '@/layers/L1/entityResolver'
import { StatsCard } from '@/shared/components/StatsCard'
function isoAfter(ms: number): string {
return new Date(Date.now() - ms).toISOString()
}
function runsHref(params: Record<string, string>): string {
const search = new URLSearchParams({
runs_sort: 'created_at',
runs_order: 'desc',
...params,
})
return `/runs?${search.toString()}`
}
const TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1000
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000
export function StatsCardRow() {
const nav = useNavigate()
// Shared, unsorted runs fetch — wireframe: "Token-summat lasketaan frontendissa
// samasta vastauksesta" (RUNNING + TODAY + FAILED + TOKEN IN/OUT all share one fetch).
const all = useEntityList('runs', {}, { sseMode: true })
const todayAfter = useMemo(
() => isoAfter(TWENTY_FOUR_HOURS_MS),
[],
)
const failedAfter = useMemo(
() => isoAfter(THIRTY_DAYS_MS),
[],
)
const runningTotal = useEntityList('runs', { phase: 'running' }, { sseMode: true })
const todayTotal = useEntityList('runs', { created_after: todayAfter }, { sseMode: true })
const failedTotal = useEntityList('runs', {
phase: 'failed',
created_after: failedAfter,
}, { sseMode: true })
const tokenTotals = useMemo(() => {
const rows = (all.data ?? []) as Array<{ token_input?: number; token_output?: number }>
let input = 0
let output = 0
for (const r of rows) {
input += r.token_input ?? 0
output += r.token_output ?? 0
}
return { input, output }
}, [all.data])
const isLoading = all.isLoading
return (
<div
className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-5"
data-testid="dashboard-stats-row"
aria-busy={isLoading}
>
<StatsCard
icon={Activity}
label="Running"
value={(runningTotal.totalEstimate ?? runningTotal.data?.length) ?? 0}
onClick={() => nav(runsHref({ runs_phase: 'running' }))}
/>
<StatsCard
icon={Calendar}
label="Today"
value={(todayTotal.totalEstimate ?? todayTotal.data?.length) ?? 0}
onClick={() => nav(runsHref({ runs_created_after: isoAfter(24 * 60 * 60 * 1000) }))}
/>
<StatsCard
icon={AlertTriangle}
label="Failed"
value={(failedTotal.totalEstimate ?? failedTotal.data?.length) ?? 0}
onClick={() => nav(runsHref({ runs_phase: 'failed', runs_created_after: isoAfter(30 * 24 * 60 * 60 * 1000) }))}
/>
<StatsCard
icon={ArrowDownToLine}
label="Token In"
value={tokenTotals.input}
/>
<StatsCard
icon={ArrowUpFromLine}
label="Token Out"
value={tokenTotals.output}
/>
</div>
)
}
|