import { useState, useCallback } from "react"; import { Play, Pause, Pencil, Trash2, Clock, CheckCircle, XCircle, ChevronDown, ChevronUp, Layers } from "lucide-react"; import type { ScheduledTask, AutomationRunResult, AutomationStepResult } from "@fusion/core"; /** * Format a duration in milliseconds to a human-readable string. */ function formatDurationMs(ms: number): string { if (ms < 1000) return `${ms}ms`; const seconds = Math.floor(ms / 1000); if (seconds < 60) return `${seconds}s`; const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (minutes < 60) return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`; const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`; } /** * Format an ISO timestamp to a relative time string. */ function relativeTime(iso: string): string { const now = Date.now(); const then = new Date(iso).getTime(); const diffMs = now - then; // Future if (diffMs < 0) { const absDiff = Math.abs(diffMs); if (absDiff < 60_000) return "in a moment"; if (absDiff < 3_600_000) return `in ${Math.floor(absDiff / 60_000)}m`; if (absDiff < 86_400_000) return `in ${Math.floor(absDiff / 3_600_000)}h`; return `in ${Math.floor(absDiff / 86_400_000)}d`; } // Past if (diffMs < 60_000) return "just now"; if (diffMs < 3_600_000) return `${Math.floor(diffMs / 60_000)}m ago`; if (diffMs < 86_400_000) return `${Math.floor(diffMs / 3_600_000)}h ago`; return `${Math.floor(diffMs / 86_400_000)}d ago`; } const SCHEDULE_TYPE_COLORS: Record = { hourly: "var(--color-blue, #3b82f6)", daily: "var(--color-green, #22c55e)", weekly: "var(--color-purple, #a855f7)", monthly: "var(--color-orange, #f97316)", custom: "var(--color-gray, #6b7280)", every15Minutes: "var(--color-cyan, #06b6d4)", every30Minutes: "var(--color-teal, #14b8a6)", every2Hours: "var(--color-indigo, #6366f1)", every6Hours: "var(--color-rose, #f43f5e)", every12Hours: "var(--color-amber, #f59e0b)", weekdays: "var(--color-emerald, #10b981)", }; interface ScheduleCardProps { schedule: ScheduledTask; onEdit: (schedule: ScheduledTask) => void; onDelete: (schedule: ScheduledTask) => void; onRun: (schedule: ScheduledTask) => void; onToggle: (schedule: ScheduledTask) => void; /** Whether a manual run is currently in progress. */ running?: boolean; } function RunResultBadge({ result }: { result: AutomationRunResult }) { const duration = new Date(result.completedAt).getTime() - new Date(result.startedAt).getTime(); return ( {result.success ? ( ) : ( )} {result.success ? "Success" : "Failed"} {formatDurationMs(duration)} ); } function StepResultIndicator({ stepResults }: { stepResults: AutomationStepResult[] }) { return ( {stepResults.map((sr) => ( ))} ); } function RunHistoryItem({ result, index }: { result: AutomationRunResult; index: number }) { const [expanded, setExpanded] = useState(false); const duration = new Date(result.completedAt).getTime() - new Date(result.startedAt).getTime(); const hasStepResults = result.stepResults && result.stepResults.length > 0; return (
{expanded && (
{hasStepResults && (
{result.stepResults!.map((sr) => (
{sr.success ? : } {sr.stepName} {sr.error && {sr.error}}
))}
)} {result.output && (
{result.output}
)} {result.error && (
{result.error}
)}
)}
); } export function ScheduleCard({ schedule, onEdit, onDelete, onRun, onToggle, running }: ScheduleCardProps) { const [showHistory, setShowHistory] = useState(false); const handleDelete = useCallback(() => { if (window.confirm(`Delete schedule "${schedule.name}"? This cannot be undone.`)) { onDelete(schedule); } }, [schedule, onDelete]); const typeColor = SCHEDULE_TYPE_COLORS[schedule.scheduleType] ?? SCHEDULE_TYPE_COLORS.custom; return (
{schedule.name} {schedule.scheduleType}
{schedule.description && (

{schedule.description}

)}
{schedule.steps && schedule.steps.length > 0 ? (
{schedule.steps.length} step{schedule.steps.length !== 1 ? "s" : ""}
) : (
{schedule.command}
)}
{schedule.cronExpression}
{schedule.nextRunAt && schedule.enabled && (
Next: {relativeTime(schedule.nextRunAt)}
)} {schedule.lastRunAt && (
Last: {relativeTime(schedule.lastRunAt)}
)} {schedule.lastRunResult && ( )}
Runs: {schedule.runCount}
{schedule.runHistory.length > 0 && (
{showHistory && (
{schedule.runHistory.slice(0, 10).map((result, i) => ( ))} {schedule.runHistory.length > 10 && (
…and {schedule.runHistory.length - 10} more
)}
)}
)}
); }