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 | 69x 69x 82x 82x 82x 82x 82x 82x 82x 82x 82x 82x 1x 410x | import { useState, useRef } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { Activity, Calendar, Ban, LogIn, LogOut, Play } from 'lucide-react'
import { useDashboardStats } from './dashboardApi'
import { useRunColumns } from '@/features/runs/hooks/useRunColumns'
import { StatsCard } from '@/shared/components/StatsCard'
import { DataTable } from '@/shared/components/DataTable'
import { CrudModal } from '@/shared/components/CrudModal'
import { Page, PageHeader } from '@/shared/components/Page'
import { RunForm } from '@/features/runs/components/RunForm'
import type { components } from '@/types/api'
import type { CrudFormRef } from '@/features/runs/components/RunForm'
import { Button } from '@/components/ui/button'
type Run = components['schemas']['Run']
export function DashboardPage() {
const [searchParams, setSearchParams] = useSearchParams()
const nav = useNavigate()
const { stats, runs, isLoading, error, refetch } = useDashboardStats()
const columns = useRunColumns()
const [formDirty, setFormDirty] = useState(false)
const [formPending, setFormPending] = useState(false)
const formRef = useRef<CrudFormRef>(null)
const statCards = [
{ icon: Activity, label: 'Running', value: stats.running, accent: 'border-blue-500', onClick: () => nav('/runs?phase=running&sort=created_at&order=desc') },
{ icon: Calendar, label: 'Today', value: stats.today, accent: 'border-gray-400', onClick: () => nav('/runs?created_after=24h&sort=created_at&order=desc') },
{ icon: Ban, label: 'Failed', value: stats.failed, accent: 'border-red-500', onClick: () => nav('/runs?phase=failed&created_after=30d&sort=created_at&order=desc') },
{ icon: LogIn, label: 'Token In', value: stats.tokenInput, accent: 'border-purple-500' },
{ icon: LogOut, label: 'Token Out', value: stats.tokenOutput, accent: 'border-orange-500' },
]
return (
<Page>
<PageHeader title="Dashboard" actions={
<Button variant="outline" onClick={() => nav('/runs?sort=created_at&order=desc')}>
View All Runs →
</Button>
} />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
{statCards.map((card) => (
<div key={card.label} onClick={card.onClick} className={card.onClick ? 'cursor-pointer' : ''}>
<StatsCard icon={card.icon} label={card.label} value={card.value} accent={card.accent} />
</div>
))}
</div>
<p className="-mt-2 text-xs text-muted-foreground">Token stats based on last 50 runs</p>
<div className="flex items-center justify-end">
<Button
onClick={() => setSearchParams(prev => { prev.set('create', 'true'); return prev })}
size="sm"
>
<Play className="mr-2 h-4 w-4" />
New Run
</Button>
</div>
<DataTable<Run>
columns={columns}
data={runs}
isLoading={isLoading}
error={error}
onRetry={refetch}
entityName="Run"
onRowClick={(item) => nav(`/runs/${item.run_id}`)}
/>
<CrudModal
open={searchParams.get('create') === 'true'}
title="Trigger Run"
isDirty={formDirty}
isPending={formPending}
saveLabel="Trigger Run"
onClose={() => setSearchParams(prev => { prev.delete('create'); return prev })}
onSave={() => formRef.current?.submit()}
>
<RunForm
ref={formRef}
onSuccess={() => setSearchParams({})}
onClose={() => setSearchParams(prev => { prev.delete('create'); return prev })}
onDirtyChange={setFormDirty}
onPendingChange={setFormPending}
/>
</CrudModal>
</Page>
)
}
|