import React, { useState, useEffect } from 'react'; import axios from '../api/client'; import { X, Calendar, Clock, RotateCw, Zap } from 'lucide-react'; interface ScheduledTask { id: string; name: string; description: string; plugin_name: string; plugin_display_name: string; schedule_type: 'ONE_TIME' | 'INTERVAL' | 'CRON'; cron_expression?: string; interval_minutes?: number; run_at?: string; next_run_at?: string; last_run_at?: string; status: 'ACTIVE' | 'PAUSED' | 'DISABLED'; last_run_status?: string; plugin_config: Record; created_at: string; updated_at: string; } interface EditTaskModalProps { task: ScheduledTask | null; isOpen: boolean; onClose: () => void; onSuccess: () => void; } // Schedule presets for visual selection interface SchedulePreset { id: string; label: string; description: string; type: 'INTERVAL' | 'CRON'; interval_minutes?: number; cron_expression?: string; } const SCHEDULE_PRESETS: SchedulePreset[] = [ { id: 'every_15min', label: 'Every 15 minutes', description: 'Runs 4 times per hour', type: 'INTERVAL', interval_minutes: 15 }, { id: 'every_30min', label: 'Every 30 minutes', description: 'Runs twice per hour', type: 'INTERVAL', interval_minutes: 30 }, { id: 'every_hour', label: 'Every hour', description: 'Runs 24 times per day', type: 'INTERVAL', interval_minutes: 60 }, { id: 'every_2hours', label: 'Every 2 hours', description: 'Runs 12 times per day', type: 'INTERVAL', interval_minutes: 120 }, { id: 'every_4hours', label: 'Every 4 hours', description: 'Runs 6 times per day', type: 'INTERVAL', interval_minutes: 240 }, { id: 'every_6hours', label: 'Every 6 hours', description: 'Runs 4 times per day', type: 'INTERVAL', interval_minutes: 360 }, { id: 'every_12hours', label: 'Twice daily', description: 'Runs at midnight and noon', type: 'INTERVAL', interval_minutes: 720 }, { id: 'daily_midnight', label: 'Daily at midnight', description: 'Runs once per day at 12:00 AM', type: 'CRON', cron_expression: '0 0 * * *' }, { id: 'daily_9am', label: 'Daily at 9 AM', description: 'Runs once per day at 9:00 AM', type: 'CRON', cron_expression: '0 9 * * *' }, { id: 'daily_6pm', label: 'Daily at 6 PM', description: 'Runs once per day at 6:00 PM', type: 'CRON', cron_expression: '0 18 * * *' }, { id: 'weekdays_9am', label: 'Weekdays at 9 AM', description: 'Mon-Fri at 9:00 AM', type: 'CRON', cron_expression: '0 9 * * 1-5' }, { id: 'weekdays_6pm', label: 'Weekdays at 6 PM', description: 'Mon-Fri at 6:00 PM', type: 'CRON', cron_expression: '0 18 * * 1-5' }, { id: 'weekly_sunday', label: 'Weekly on Sunday', description: 'Every Sunday at midnight', type: 'CRON', cron_expression: '0 0 * * 0' }, { id: 'weekly_monday', label: 'Weekly on Monday', description: 'Every Monday at 9:00 AM', type: 'CRON', cron_expression: '0 9 * * 1' }, { id: 'monthly_1st', label: 'Monthly on the 1st', description: 'First day of each month', type: 'CRON', cron_expression: '0 0 1 * *' }, ]; const EditTaskModal: React.FC = ({ task, isOpen, onClose, onSuccess }) => { const [taskName, setTaskName] = useState(''); const [description, setDescription] = useState(''); const [selectedPreset, setSelectedPreset] = useState(''); const [scheduleMode, setScheduleMode] = useState<'preset' | 'onetime' | 'advanced'>('preset'); const [runAtDate, setRunAtDate] = useState(''); const [runAtTime, setRunAtTime] = useState(''); const [customCron, setCustomCron] = useState('0 0 * * *'); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(''); // Load task data when modal opens useEffect(() => { if (task && isOpen) { setTaskName(task.name); setDescription(task.description || ''); // Determine schedule mode and preset if (task.schedule_type === 'ONE_TIME') { setScheduleMode('onetime'); if (task.run_at) { const date = new Date(task.run_at); setRunAtDate(date.toISOString().split('T')[0]); setRunAtTime(date.toTimeString().slice(0, 5)); } } else if (task.schedule_type === 'INTERVAL') { // Try to match to a preset const matchingPreset = SCHEDULE_PRESETS.find( p => p.type === 'INTERVAL' && p.interval_minutes === task.interval_minutes ); if (matchingPreset) { setScheduleMode('preset'); setSelectedPreset(matchingPreset.id); } else { setScheduleMode('advanced'); // Convert interval to rough cron (not perfect, but gives user a starting point) setCustomCron(`*/${task.interval_minutes} * * * *`); } } else if (task.schedule_type === 'CRON' && task.cron_expression) { // Try to match to a preset const matchingPreset = SCHEDULE_PRESETS.find( p => p.type === 'CRON' && p.cron_expression === task.cron_expression ); if (matchingPreset) { setScheduleMode('preset'); setSelectedPreset(matchingPreset.id); } else { setScheduleMode('advanced'); setCustomCron(task.cron_expression); } } } }, [task, isOpen]); const handleClose = () => { setError(''); onClose(); }; const getScheduleDescription = () => { if (scheduleMode === 'onetime') { if (runAtDate && runAtTime) { return `Once on ${new Date(`${runAtDate}T${runAtTime}`).toLocaleString()}`; } return 'Select date and time'; } if (scheduleMode === 'advanced') { return `Custom: ${customCron}`; } const preset = SCHEDULE_PRESETS.find(p => p.id === selectedPreset); return preset?.description || 'Select a schedule'; }; const handleSubmit = async () => { if (!task) return; setIsSubmitting(true); setError(''); try { let payload: any = { name: taskName, description, }; if (scheduleMode === 'onetime') { payload.schedule_type = 'ONE_TIME'; payload.run_at = `${runAtDate}T${runAtTime}:00`; payload.interval_minutes = null; payload.cron_expression = null; } else if (scheduleMode === 'advanced') { payload.schedule_type = 'CRON'; payload.cron_expression = customCron; payload.interval_minutes = null; payload.run_at = null; } else { const preset = SCHEDULE_PRESETS.find(p => p.id === selectedPreset); if (preset) { payload.schedule_type = preset.type; if (preset.type === 'INTERVAL') { payload.interval_minutes = preset.interval_minutes; payload.cron_expression = null; } else { payload.cron_expression = preset.cron_expression; payload.interval_minutes = null; } payload.run_at = null; } } await axios.patch(`/scheduled-tasks/${task.id}/`, payload); onSuccess(); handleClose(); } catch (err: any) { setError(err.response?.data?.detail || 'Failed to update task'); } finally { setIsSubmitting(false); } }; if (!isOpen || !task) return null; return (
{/* Header */}

Edit Task

{/* Content */}
{/* Plugin Info (read-only) */}

Plugin

{task.plugin_display_name}

{/* Task Name */}
setTaskName(e.target.value)} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white" />
{/* Schedule Mode Tabs */}
{/* Preset Selection */} {scheduleMode === 'preset' && (
{SCHEDULE_PRESETS.map((preset) => ( ))}
)} {/* One-Time Selection */} {scheduleMode === 'onetime' && (
setRunAtDate(e.target.value)} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white" />
setRunAtTime(e.target.value)} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white" />
)} {/* Advanced Cron */} {scheduleMode === 'advanced' && (
setCustomCron(e.target.value)} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white font-mono text-sm" placeholder="0 0 * * *" />

Format: minute hour day month weekday (e.g., "0 9 * * 1-5" = weekdays at 9 AM)

)}
{/* Schedule Preview */}
Schedule: {getScheduleDescription()}
{/* Error */} {error && (

{error}

)}
{/* Footer */}
); }; export default EditTaskModal;