'use client'; import { useState, type ReactNode } from 'react'; import { Activity, CheckCircle, Clock, XCircle } from 'lucide-react'; import { Button } from '../ui/button'; import { Card, CardContent } from '../ui/card'; import { Skeleton } from '../ui/skeleton'; import { cn } from '../../lib/utils'; /** * Minimal, in-package shape of the workflow execution stats payload. Mirrors * the subset of the backend stats record this overview actually reads — kept * local so the component carries no app/Redux coupling. */ export interface WorkflowStats { totals: { total: number; completed: number; failed: number; }; rates: { successRate: number; failureRate: number; }; duration: { avgSeconds: number | null; minSeconds: number | null; maxSeconds: number | null; }; } export interface WorkflowStatsOverviewProps { /** Execution stats to render. When undefined, cards show a skeleton/empty value. */ stats?: WorkflowStats | null; /** Whether stats are currently loading (drives the per-card skeleton). */ isLoading?: boolean; /** * Optional refresh callback. When provided, changing the period selector * invokes it with the selected period so the consumer can refetch. */ onRefresh?: (period: string) => Promise; } const PERIOD_OPTIONS = [ { label: '7d', value: '7d' }, { label: '30d', value: '30d' }, { label: '90d', value: '90d' }, ] as const; function formatDuration(seconds: number | null | undefined): string { if (seconds === null || seconds === undefined || seconds === 0) return '0s'; if (seconds < 60) return `${Math.round(seconds)}s`; const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.round(seconds % 60); if (remainingSeconds === 0) return `${minutes}m`; return `${minutes}m ${remainingSeconds}s`; } /** * Inline metric card — substitute for the app's `MetricsCard` primitive, * which is not part of @startsimpli/ui. Renders a titled value with an icon, * optional description, and a loading skeleton. */ function MetricCard({ title, value, icon, description, loading, }: { title: string; value: ReactNode; icon: ReactNode; description?: ReactNode; loading?: boolean; }) { return (
{title} {icon}
{loading ? ( ) : (
{value ?? '—'}
)} {description && !loading && (

{description}

)}
); } export function WorkflowStatsOverview({ stats, isLoading = false, onRefresh, }: WorkflowStatsOverviewProps) { const [period, setPeriod] = useState('30d'); const handlePeriodChange = (value: string) => { setPeriod(value); void onRefresh?.(value); }; return (

Execution Metrics

{PERIOD_OPTIONS.map((opt) => ( ))}
} description={`Last ${period}`} loading={isLoading} /> } description={`${stats?.totals.completed ?? 0} completed`} loading={isLoading} /> } description={ stats ? `${formatDuration(stats.duration.minSeconds)} - ${formatDuration(stats.duration.maxSeconds)}` : undefined } loading={isLoading} /> } description={ stats ? `${stats.rates.failureRate}% failure rate` : undefined } loading={isLoading} />
); }