import { useState, useRef, useEffect } from 'react' import type { CronTask } from '../../types/task' import { useTaskStore } from '../../stores/taskStore' import { useTranslation } from '../../i18n' import { describeCron } from '../../lib/cronDescribe' import { TaskRunsPanel } from './TaskRunsPanel' import { NewTaskModal } from './NewTaskModal' type Props = { task: CronTask showLogs: boolean onToggleLogs: () => void } type ConfirmAction = 'run' | 'toggle' | 'delete' | null export function TaskRow({ task, showLogs, onToggleLogs }: Props) { const { deleteTask, updateTask, runTask } = useTaskStore() const t = useTranslation() const [showEdit, setShowEdit] = useState(false) const [showMenu, setShowMenu] = useState(false) const [isRunning, setIsRunning] = useState(false) const [confirmAction, setConfirmAction] = useState(null) const [logsRefreshKey, setLogsRefreshKey] = useState(0) const menuRef = useRef(null) const confirmRef = useRef(null) // Close menu / confirm on outside click useEffect(() => { if (!showMenu && !confirmAction) return const handler = (e: MouseEvent) => { const target = e.target as Node if (showMenu && menuRef.current && !menuRef.current.contains(target)) { setShowMenu(false) } if (confirmAction && confirmRef.current && !confirmRef.current.contains(target)) { setConfirmAction(null) } } document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) }, [showMenu, confirmAction]) const handleRunNow = async () => { setConfirmAction(null) setIsRunning(true) if (!showLogs) onToggleLogs() // open logs panel (accordion will close others) try { await runTask(task.id) setLogsRefreshKey((k) => k + 1) } catch (err) { console.error('Failed to run task:', err) } finally { setIsRunning(false) } } const handleToggle = () => { setConfirmAction(null) setShowMenu(false) updateTask(task.id, { enabled: !task.enabled }) } const handleDelete = () => { setConfirmAction(null) setShowMenu(false) deleteTask(task.id) } const iconBtn = 'p-1.5 rounded-[var(--radius-sm)] transition-colors' const menuItem = 'flex items-center gap-2.5 w-full px-3 py-2 text-xs text-left rounded-[var(--radius-sm)] transition-colors' return (
{/* Left: status + info */}
{task.name}
{task.description && (
{task.description}
)}
{t('tasks.createdAt')}{new Date(task.createdAt).toLocaleDateString()} {task.lastFiredAt && ( {t('tasks.lastRunAt')}{new Date(task.lastFiredAt).toLocaleDateString()} )}
{/* Right: cron + actions */}
{describeCron(task.cron, t)}
{/* Run Now */}
{confirmAction === 'run' && ( setConfirmAction(null)} cancelLabel={t('common.cancel')} /> )}
{/* View Logs */} {/* More menu */}
{showMenu && !confirmAction && (
{/* Edit */} {/* Toggle */}
{/* Delete */}
)} {/* Confirm popovers for menu actions */} {confirmAction === 'toggle' && (
{ setConfirmAction(null); setShowMenu(false) }} cancelLabel={t('common.cancel')} />
)} {confirmAction === 'delete' && (
{ setConfirmAction(null); setShowMenu(false) }} cancelLabel={t('common.cancel')} variant="error" />
)}
{/* Runs panel */} {showLogs && (
)} {/* Edit modal */} {showEdit && ( setShowEdit(false)} /> )}
) } // ─── Confirm Popover ───────────────────────────────────────────────────────── function ConfirmPopover({ message, confirmLabel, onConfirm, onCancel, cancelLabel, variant = 'brand' }: { message: string confirmLabel: string onConfirm: () => void onCancel: () => void cancelLabel: string variant?: 'brand' | 'error' }) { return (

{message}

) }