140 lines
4.1 KiB
TypeScript
140 lines
4.1 KiB
TypeScript
import React from 'react'
|
|
import { FileText, CheckCircle, AlertCircle, Clock, RefreshCw } from 'lucide-react'
|
|
import {
|
|
StatsCard,
|
|
DataQualityPanel,
|
|
ActiveModelPanel,
|
|
RecentActivityPanel,
|
|
SystemStatusBar,
|
|
} from './dashboard/index'
|
|
import { useDashboard } from '../hooks/useDashboard'
|
|
|
|
interface DashboardOverviewProps {
|
|
onNavigate: (view: string) => void
|
|
}
|
|
|
|
export const DashboardOverview: React.FC<DashboardOverviewProps> = ({ onNavigate }) => {
|
|
const {
|
|
stats,
|
|
model,
|
|
runningTraining,
|
|
activities,
|
|
isLoading,
|
|
error,
|
|
} = useDashboard()
|
|
|
|
const handleStatsClick = (filter?: string) => {
|
|
if (filter) {
|
|
onNavigate(`documents?status=${filter}`)
|
|
} else {
|
|
onNavigate('documents')
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="p-8 max-w-7xl mx-auto">
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
|
|
<AlertCircle className="w-12 h-12 text-red-500 mx-auto mb-4" />
|
|
<h2 className="text-lg font-semibold text-red-800 mb-2">
|
|
Failed to load dashboard
|
|
</h2>
|
|
<p className="text-sm text-red-600 mb-4">
|
|
{error instanceof Error ? error.message : 'An unexpected error occurred'}
|
|
</p>
|
|
<button
|
|
onClick={() => window.location.reload()}
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-red-100 hover:bg-red-200 text-red-800 rounded-md text-sm font-medium transition-colors"
|
|
>
|
|
<RefreshCw className="w-4 h-4" />
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="p-8 max-w-7xl mx-auto animate-fade-in">
|
|
{/* Header */}
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold text-warm-text-primary tracking-tight">
|
|
Dashboard
|
|
</h1>
|
|
<p className="text-sm text-warm-text-muted mt-1">
|
|
Overview of your document annotation system
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stats Cards Row */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
<StatsCard
|
|
label="Total Documents"
|
|
value={stats?.total_documents ?? 0}
|
|
icon={FileText}
|
|
iconColor="text-warm-text-primary"
|
|
iconBgColor="bg-warm-bg"
|
|
isLoading={isLoading}
|
|
onClick={() => handleStatsClick()}
|
|
/>
|
|
<StatsCard
|
|
label="Complete"
|
|
value={stats?.annotation_complete ?? 0}
|
|
icon={CheckCircle}
|
|
iconColor="text-warm-state-success"
|
|
iconBgColor="bg-green-50"
|
|
isLoading={isLoading}
|
|
onClick={() => handleStatsClick('labeled')}
|
|
/>
|
|
<StatsCard
|
|
label="Incomplete"
|
|
value={stats?.annotation_incomplete ?? 0}
|
|
icon={AlertCircle}
|
|
iconColor="text-orange-600"
|
|
iconBgColor="bg-orange-50"
|
|
isLoading={isLoading}
|
|
onClick={() => handleStatsClick('labeled')}
|
|
/>
|
|
<StatsCard
|
|
label="Pending"
|
|
value={stats?.pending ?? 0}
|
|
icon={Clock}
|
|
iconColor="text-blue-600"
|
|
iconBgColor="bg-blue-50"
|
|
isLoading={isLoading}
|
|
onClick={() => handleStatsClick('pending')}
|
|
/>
|
|
</div>
|
|
|
|
{/* Two-column layout: Data Quality + Active Model */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
<DataQualityPanel
|
|
completenessRate={stats?.completeness_rate ?? 0}
|
|
completeCount={stats?.annotation_complete ?? 0}
|
|
incompleteCount={stats?.annotation_incomplete ?? 0}
|
|
pendingCount={stats?.pending ?? 0}
|
|
isLoading={isLoading}
|
|
onViewIncomplete={() => handleStatsClick('labeled')}
|
|
/>
|
|
<ActiveModelPanel
|
|
model={model}
|
|
runningTraining={runningTraining}
|
|
isLoading={isLoading}
|
|
onGoToTraining={() => onNavigate('training')}
|
|
/>
|
|
</div>
|
|
|
|
{/* Recent Activity */}
|
|
<div className="mb-8">
|
|
<RecentActivityPanel
|
|
activities={activities}
|
|
isLoading={isLoading}
|
|
/>
|
|
</div>
|
|
|
|
{/* System Status */}
|
|
<SystemStatusBar />
|
|
</div>
|
|
)
|
|
}
|