// ======================================== // QueueListColumn Component // ======================================== // Queue items list for embedding in the Issues dual-column panel. // Unified data source: queueSchedulerStore only. // Includes inline scheduler controls at the bottom. import { useMemo, useCallback, useState } from 'react'; import { useIntl } from 'react-intl'; import { ListChecks, Loader2, CheckCircle, XCircle, Zap, Ban, Square, Terminal, Timer, Clock, Play, Pause, StopCircle, } from 'lucide-react'; import { Badge } from '@/components/ui/Badge'; import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogAction, AlertDialogCancel, } from '@/components/ui/AlertDialog'; import { cn } from '@/lib/utils'; import { useIssueQueueIntegrationStore, selectAssociationChain, } from '@/stores/issueQueueIntegrationStore'; import { useQueueExecutionStore, selectByQueueItem, } from '@/stores/queueExecutionStore'; import { useQueueSchedulerStore, selectQueueSchedulerStatus, selectQueueItems, selectSchedulerProgress, selectCurrentConcurrency, selectSchedulerConfig, } from '@/stores/queueSchedulerStore'; import type { QueueItem, QueueItemStatus } from '@/types/queue-frontend-types'; // ========== Status Config ========== const STATUS_CONFIG: Record = { pending: { variant: 'secondary', icon: Clock, label: 'Pending' }, queued: { variant: 'info', icon: Timer, label: 'Queued' }, ready: { variant: 'info', icon: Zap, label: 'Ready' }, blocked: { variant: 'outline', icon: Ban, label: 'Blocked' }, executing: { variant: 'warning', icon: Loader2, label: 'Executing' }, completed: { variant: 'success', icon: CheckCircle, label: 'Completed' }, failed: { variant: 'destructive', icon: XCircle, label: 'Failed' }, cancelled: { variant: 'secondary', icon: Square, label: 'Cancelled' }, }; // ========== Scheduler Status Styles ========== const SCHEDULER_STATUS_STYLE: Record = { idle: 'bg-muted text-muted-foreground', running: 'bg-blue-500/15 text-blue-600', paused: 'bg-yellow-500/15 text-yellow-600', stopping: 'bg-orange-500/15 text-orange-600', completed: 'bg-green-500/15 text-green-600', failed: 'bg-red-500/15 text-red-600', }; // ========== Item Row ========== function QueueItemRow({ item, isHighlighted, onSelect, }: { item: QueueItem; isHighlighted: boolean; onSelect: () => void; }) { const { formatMessage } = useIntl(); const config = STATUS_CONFIG[item.status] ?? STATUS_CONFIG.pending; const StatusIcon = config.icon; const executions = useQueueExecutionStore(selectByQueueItem(item.item_id)); const activeExec = executions.find((e) => e.status === 'running') ?? executions[0]; const sessionKey = item.sessionKey ?? activeExec?.sessionKey; const isExecuting = item.status === 'executing'; const isBlocked = item.status === 'blocked'; // Show issue_id if available (for items added from IssuePanel) const displayId = item.issue_id ? `${item.issue_id}` : item.item_id; return ( ); } // ========== Inline Scheduler Controls ========== function SchedulerBar() { const { formatMessage } = useIntl(); const status = useQueueSchedulerStore(selectQueueSchedulerStatus); const progress = useQueueSchedulerStore(selectSchedulerProgress); const concurrency = useQueueSchedulerStore(selectCurrentConcurrency); const config = useQueueSchedulerStore(selectSchedulerConfig); const startQueue = useQueueSchedulerStore((s) => s.startQueue); const pauseQueue = useQueueSchedulerStore((s) => s.pauseQueue); const stopQueue = useQueueSchedulerStore((s) => s.stopQueue); const items = useQueueSchedulerStore(selectQueueItems); const canStart = status === 'idle' && items.length > 0; const canPause = status === 'running'; const canResume = status === 'paused'; const canStop = status === 'running' || status === 'paused'; const isActive = status !== 'idle'; const [isStopConfirmOpen, setIsStopConfirmOpen] = useState(false); const handleStart = useCallback(() => { if (canResume) { startQueue(); } else if (canStart) { startQueue(items); } }, [canResume, canStart, startQueue, items]); return (
{/* Status badge */} {formatMessage({ id: `terminalDashboard.queuePanel.scheduler.status.${status}`, defaultMessage: status })} {/* Progress + Concurrency */} {isActive && ( {progress}% | {concurrency}/{config.maxConcurrentSessions} )} {/* Controls */}
{(canStart || canResume) && ( )} {canPause && ( )} {canStop && ( )}
{/* Progress bar */} {isActive && (
)} {/* Stop confirmation dialog */} {formatMessage({ id: 'terminalDashboard.queuePanel.scheduler.stopConfirmTitle', defaultMessage: 'Stop Queue?' })} {formatMessage({ id: 'terminalDashboard.queuePanel.scheduler.stopConfirmMessage', defaultMessage: 'Executing tasks will finish, but no new tasks will be started.' })} {formatMessage({ id: 'common.cancel', defaultMessage: 'Cancel' })} { stopQueue(); setIsStopConfirmOpen(false); }} > {formatMessage({ id: 'terminalDashboard.queuePanel.scheduler.stop', defaultMessage: 'Stop' })}
); } // ========== Main Component ========== export function QueueListColumn() { const { formatMessage } = useIntl(); const items = useQueueSchedulerStore(selectQueueItems); const associationChain = useIssueQueueIntegrationStore(selectAssociationChain); const buildAssociationChain = useIssueQueueIntegrationStore((s) => s.buildAssociationChain); const sortedItems = useMemo( () => [...items].sort((a, b) => a.execution_order - b.execution_order), [items] ); const handleSelect = useCallback( (queueItemId: string) => { buildAssociationChain(queueItemId, 'queue'); }, [buildAssociationChain] ); return (
{/* Item list */} {sortedItems.length === 0 ? (

{formatMessage({ id: 'terminalDashboard.queuePanel.noItems' })}

{formatMessage({ id: 'terminalDashboard.queuePanel.noItemsDesc', defaultMessage: 'Select issues and click Queue to add items' })}

) : (
{sortedItems.map((item) => ( handleSelect(item.item_id)} /> ))}
)} {/* Inline scheduler controls */}
); }