'use client'; import { useState, useEffect } from 'react'; import { useParams, useRouter } from 'next/navigation'; import Link from 'next/link'; import { ArrowLeft, CheckCircle, XCircle, Clock, PlayCircle, Loader, RefreshCw, AlertCircle, Play, ChevronRight, Layers, LayoutList, LayoutGrid, } from 'lucide-react'; import type { ExecutionStatus, PipelineDetail } from '@/lib/pipeline-types'; import { getPipeline, listExecutions } from '@/lib/pipeline-api'; import ExecutionsView from '@/components/ExecutionsView'; // Status badge component function StatusBadge({ status }: { status: ExecutionStatus['status'] }) { const config = { pending: { icon: Clock, color: 'bg-gray-100 text-gray-600', }, running: { icon: Loader, color: 'bg-blue-100 text-blue-600', }, completed: { icon: CheckCircle, color: 'bg-green-100 text-green-600', }, failed: { icon: XCircle, color: 'bg-red-100 text-red-600', }, cancelled: { icon: AlertCircle, color: 'bg-yellow-100 text-yellow-600', }, }; const { icon: Icon, color } = config[status] || config.pending; return ( {status.charAt(0).toUpperCase() + status.slice(1)} ); } // Format date string function formatDate(dateStr: string | undefined): string { if (!dateStr) return '-'; const date = new Date(dateStr); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } // Calculate duration function formatDuration(start?: string, end?: string): string { if (!start) return '-'; const startDate = new Date(start); const endDate = end ? new Date(end) : new Date(); const ms = endDate.getTime() - startDate.getTime(); if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; return `${(ms / 60000).toFixed(1)}m`; } // Execution card component function ExecutionCard({ execution, pipelineId, onClick }: { execution: ExecutionStatus; pipelineId: string; onClick: () => void; }) { return (
{formatDate(execution.started_at)}
{execution.job_id ? (
Job: e.stopPropagation()} className="text-blue-600 hover:underline font-medium" > {execution.job_id.slice(0, 8)}...
) : execution.business_id ? (
Business: {execution.business_id}
) : (
Manual execution
)}
{execution.stages_completed.length} / {execution.stages_requested.length} stages
{formatDuration(execution.started_at, execution.completed_at)}
{execution.error_message && (

{execution.error_message}

)}
{execution.id.slice(0, 12)}...
); } // View mode toggle component function ViewToggle({ mode, onChange }: { mode: 'table' | 'cards'; onChange: (mode: 'table' | 'cards') => void; }) { return (
); } /** * Pipeline detail page - shows executions like Jobs page. */ export default function PipelinePage() { const params = useParams(); const router = useRouter(); const pipelineId = params.pipelineId as string; const [pipeline, setPipeline] = useState(null); const [executions, setExecutions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [viewMode, setViewMode] = useState<'table' | 'cards'>('table'); const fetchData = async () => { setLoading(true); setError(null); try { const [pipelineData, executionsData] = await Promise.all([ getPipeline(pipelineId), listExecutions(pipelineId, { limit: 100 }), ]); setPipeline(pipelineData); setExecutions(executionsData); } catch (e) { setError(e instanceof Error ? e.message : 'Failed to load data'); } finally { setLoading(false); } }; useEffect(() => { if (pipelineId) { fetchData(); } }, [pipelineId]); const handleExecutionClick = (execution: ExecutionStatus) => { router.push(`/pipelines/${pipelineId}/executions/${execution.id}`); }; const handleRunPipeline = () => { router.push(`/pipelines/${pipelineId}/run`); }; // Count executions by status const statusCounts = { total: executions.length, completed: executions.filter(e => e.status === 'completed').length, failed: executions.filter(e => e.status === 'failed').length, running: executions.filter(e => e.status === 'running').length, }; if (loading && !pipeline) { return (
{[1, 2, 3].map((i) => (
))}
); } if (error && !pipeline) { return (
Back to Pipelines

{error}

); } return (
{/* Navigation */}
Back to Pipelines
{/* Pipeline Header */} {pipeline && (

{pipeline.name}

{pipeline.description}

{pipeline.is_enabled ? 'Enabled' : 'Disabled'}

v{pipeline.version}

Stages:
{pipeline.stages.map((stage, index) => ( {index + 1}. {stage} ))}
)} {/* Stats Row */}
{statusCounts.total}
Total Runs
{statusCounts.completed}
Completed
{statusCounts.failed}
Failed
{statusCounts.running}
Running
{/* Executions Header with View Toggle */}

Executions

{/* Executions Content */} {loading ? (
) : executions.length === 0 ? (

No Executions Yet

Run your first pipeline execution to see results here

) : viewMode === 'table' ? ( ) : (
{executions.map((execution) => ( handleExecutionClick(execution)} /> ))}
)}
); }