feat(I): I-ONB/I-UI/I-DASH frontend — SetupWizard, WorkstreamBlockRenderer, Dashboard platform section, API hooks, i18n, tsc clean, 11/11 dashboard tests

This commit is contained in:
Agent Zero
2026-08-19 01:33:23 +02:00
parent 92ac6229d2
commit 0670fdb437
6 changed files with 879 additions and 4 deletions
+224
View File
@@ -1,10 +1,26 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import {
Bot,
Workflow,
Search,
BookOpen,
DollarSign,
HeartPulse,
Loader2,
} from 'lucide-react';
import { StatCard } from '@/components/shared/StatCard';
import { ActivityFeed, ActivityItem } from '@/components/shared/ActivityFeed';
import { DashboardGrid } from '@/components/dashboard/DashboardGrid';
import { Card } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { useUnifiedContacts, useAuditLog } from '@/api/hooks';
import { useDashboardWidgets } from '@/api/dashboard';
import {
usePlatformDashboard,
useCostDashboard,
useUsageAnalytics,
} from '@/api/platform';
import { formatDateTime } from '@/utils/date';
export function DashboardPage() {
@@ -14,6 +30,22 @@ export function DashboardPage() {
const { data: auditData, isError: auditError } = useAuditLog(1, 5);
const { data: widgetsData, isError: widgetsError } = useDashboardWidgets();
const {
data: platformData,
isLoading: platformLoading,
isError: platformError,
} = usePlatformDashboard();
const {
data: costData,
isLoading: costLoading,
isError: costError,
} = useCostDashboard(30);
const {
data: usageData,
isLoading: usageLoading,
isError: usageError,
} = useUsageAnalytics(30);
const totalCompanies = companiesData?.total ?? 0;
const totalContacts = contactsData?.total ?? 0;
@@ -46,6 +78,20 @@ export function DashboardPage() {
const widgets = widgetsData?.items ?? [];
const agents = platformData?.agents;
const workflows = platformData?.workflows;
const search = platformData?.search;
const knowledge = platformData?.knowledge;
const systemHealth = platformData?.system_health;
const searchQueries = usageData?.search_queries;
const costUsd = costData?.total_cost_usd ?? 0;
const budget = costData?.budget;
const budgetUtilization = budget?.utilization_pct ?? 0;
const healthLabel = systemHealth?.status ?? 'unknown';
const healthVariant = healthStatusVariant(healthLabel);
return (
<div className="p-6 max-w-7xl mx-auto" data-testid="dashboard-page">
<h1 className="text-2xl font-bold text-secondary-900 mb-6">{t('dashboard.title')}</h1>
@@ -73,6 +119,155 @@ export function DashboardPage() {
/>
</div>
{/* Platform Dashboard — Phase I.7 (I-DASH) */}
<section className="mb-8" aria-label={t('dashboard.platform.title')}>
<h2 className="text-lg font-semibold text-secondary-800 mb-4">
{t('dashboard.platform.title')}
</h2>
{platformLoading || costLoading || usageLoading ? (
<div className="flex items-center justify-center py-12">
<Loader2 className="animate-spin h-6 w-6 text-primary-500" aria-hidden="true" />
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Agent status */}
<Card title={t('dashboard.platform.agents')}>
{agents?.error || platformError ? (
<p className="text-sm text-secondary-500">{t('dashboard.platform.unavailable')}</p>
) : (
<div className="space-y-3">
<MetricRow
icon={<Bot className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.activeAgents')}
value={agents?.active_agents ?? 0}
/>
<MetricRow
icon={<Bot className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.totalRuns')}
value={agents?.total_runs ?? 0}
/>
<MetricRow
icon={<Bot className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.recentRuns7d')}
value={agents?.recent_runs_7d ?? 0}
/>
</div>
)}
</Card>
{/* Workflow stats */}
<Card title={t('dashboard.platform.workflows')}>
{workflows?.error || platformError ? (
<p className="text-sm text-secondary-500">{t('dashboard.platform.unavailable')}</p>
) : (
<div className="space-y-3">
<MetricRow
icon={<Workflow className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.activeWorkflows')}
value={workflows?.active_workflows ?? 0}
/>
<MetricRow
icon={<Workflow className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.runningInstances')}
value={workflows?.running_instances ?? 0}
/>
<MetricRow
icon={<Workflow className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.completedInstances')}
value={workflows?.completed_instances ?? 0}
/>
</div>
)}
</Card>
{/* Search metrics */}
<Card title={t('dashboard.platform.search')}>
{usageError || searchQueries?.error ? (
<p className="text-sm text-secondary-500">{t('dashboard.platform.unavailable')}</p>
) : (
<div className="space-y-3">
<MetricRow
icon={<Search className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.totalQueries')}
value={searchQueries?.total ?? search?.total_queries ?? 0}
/>
<MetricRow
icon={<Search className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.avgLatency')}
value={
typeof search?.avg_latency_ms === 'number'
? `${search.avg_latency_ms} ms`
: '—'
}
/>
</div>
)}
</Card>
{/* Knowledge coverage */}
<Card title={t('dashboard.platform.knowledge')}>
{platformError || knowledge?.error ? (
<p className="text-sm text-secondary-500">{t('dashboard.platform.unavailable')}</p>
) : (
<div className="space-y-3">
<MetricRow
icon={<BookOpen className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.wikiArticles')}
value={knowledge?.wiki_articles ?? 0}
/>
<MetricRow
icon={<BookOpen className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.coverage')}
value={
typeof knowledge?.coverage_pct === 'number'
? `${knowledge.coverage_pct}%`
: '—'
}
/>
</div>
)}
</Card>
{/* Cost tracking */}
<Card title={t('dashboard.platform.cost')}>
{costError || costData?.error ? (
<p className="text-sm text-secondary-500">{t('dashboard.platform.unavailable')}</p>
) : (
<div className="space-y-3">
<MetricRow
icon={<DollarSign className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.totalCost')}
value={`$${costUsd.toFixed(2)}`}
/>
{budget?.monthly_limit_usd ? (
<MetricRow
icon={<DollarSign className="h-4 w-4" aria-hidden="true" />}
label={t('dashboard.platform.budgetUtilization')}
value={`${budgetUtilization.toFixed(1)}%`}
/>
) : null}
</div>
)}
</Card>
{/* System health */}
<Card title={t('dashboard.platform.systemHealth')}>
{platformError ? (
<p className="text-sm text-secondary-500">{t('dashboard.platform.unavailable')}</p>
) : (
<div className="flex items-center gap-2">
<HeartPulse className="h-4 w-4 text-primary-500" aria-hidden="true" />
<Badge variant={healthVariant} dot>
{t(`dashboard.platform.health.${healthLabel}`)}
</Badge>
</div>
)}
</Card>
</div>
)}
</section>
{/* Dynamic Plugin Widgets */}
{widgetsError ? (
<p className="text-sm text-secondary-500 mb-6" data-testid="dashboard-widgets-unavailable">
@@ -95,3 +290,32 @@ export function DashboardPage() {
</div>
);
}
function MetricRow({
icon,
label,
value,
}: {
icon: React.ReactNode;
label: string;
value: string | number;
}) {
return (
<div className="flex items-center justify-between">
<span className="flex items-center gap-2 text-sm text-secondary-600">
<span className="text-primary-500" aria-hidden="true">{icon}</span>
{label}
</span>
<span className="text-sm font-semibold text-secondary-900">{value}</span>
</div>
);
}
function healthStatusVariant(status: string): 'success' | 'warning' | 'danger' | 'secondary' {
if (status === 'healthy') return 'success';
if (status === 'degraded') return 'warning';
if (status === 'down') return 'danger';
return 'secondary';
}
export default DashboardPage;