All files / features / dashboard StatsCardRow.tsx

77.55% Statements 38/49
59.25% Branches 16/27
64.28% Functions 9/14
80% Lines 36/45

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            55x   55x   216x                   55x 55x   180x 180x     180x 180x 180x 180x 180x 180x       180x 260x 260x 260x 260x 640x 640x   260x         180x 180x                                                                                            
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>
  )
}