import React, { useState } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import axios from '../api/client'; import { X, Calendar, Clock, RotateCw, Zap, CalendarDays } from 'lucide-react'; import toast from 'react-hot-toast'; import { SCHEDULE_PRESETS, TRIGGER_OPTIONS, OFFSET_PRESETS, getScheduleDescription, getEventTimingDescription, } from '../constants/schedulePresets'; import { ErrorMessage } from './ui'; interface PluginInstallation { id: string; template: number; template_name: string; template_slug: string; template_description: string; category: string; version: string; author_name: string; logo_url?: string; template_variables: Record; scheduled_task?: number; scheduled_task_name?: string; installed_at: string; config_values: Record; has_update: boolean; } interface CreateTaskModalProps { isOpen: boolean; onClose: () => void; onSuccess: () => void; } // Task type: scheduled or event-based type TaskType = 'scheduled' | 'event'; const CreateTaskModal: React.FC = ({ isOpen, onClose, onSuccess }) => { const queryClient = useQueryClient(); const [step, setStep] = useState(1); const [selectedPlugin, setSelectedPlugin] = useState(null); const [taskName, setTaskName] = useState(''); const [description, setDescription] = useState(''); // Task type selection const [taskType, setTaskType] = useState('scheduled'); // Schedule selection (for scheduled tasks) const [selectedPreset, setSelectedPreset] = useState('every_hour'); const [scheduleMode, setScheduleMode] = useState<'preset' | 'onetime' | 'advanced'>('preset'); // One-time schedule const [runAtDate, setRunAtDate] = useState(''); const [runAtTime, setRunAtTime] = useState(''); // Advanced (custom cron) const [customCron, setCustomCron] = useState('0 0 * * *'); // Event automation settings (for event tasks) const [selectedTrigger, setSelectedTrigger] = useState('at_start'); const [selectedOffset, setSelectedOffset] = useState(0); const [applyToExisting, setApplyToExisting] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(''); // Fetch available plugins const { data: plugins = [], isLoading: pluginsLoading } = useQuery({ queryKey: ['plugin-installations'], queryFn: async () => { const { data } = await axios.get('/plugin-installations/'); // Filter out plugins that already have scheduled tasks return data.filter((p: PluginInstallation) => !p.scheduled_task); }, enabled: isOpen, }); const handleClose = () => { setStep(1); setSelectedPlugin(null); setTaskName(''); setDescription(''); setError(''); setTaskType('scheduled'); setScheduleMode('preset'); setSelectedPreset('every_hour'); setSelectedTrigger('at_start'); setSelectedOffset(0); setApplyToExisting(true); setRunAtDate(''); setRunAtTime(''); setCustomCron('0 0 * * *'); onClose(); }; const handlePluginSelect = (plugin: PluginInstallation) => { setSelectedPlugin(plugin); setTaskName(`${plugin.template_name} - Scheduled Task`); setStep(2); }; // Use shared helper functions from constants const scheduleDescriptionText = getScheduleDescription( scheduleMode, selectedPreset, runAtDate, runAtTime, customCron ); const eventTimingDescriptionText = getEventTimingDescription(selectedTrigger, selectedOffset); const showOffset = !['on_complete', 'on_cancel'].includes(selectedTrigger); const handleSubmit = async () => { if (!selectedPlugin) return; setIsSubmitting(true); setError(''); try { if (taskType === 'event') { // Create global event plugin (applies to all events) const payload = { plugin_installation: selectedPlugin.id, trigger: selectedTrigger, offset_minutes: selectedOffset, is_active: true, apply_to_existing: applyToExisting, }; await axios.post('/global-event-plugins/', payload); queryClient.invalidateQueries({ queryKey: ['global-event-plugins'] }); toast.success(applyToExisting ? 'Plugin attached to all events' : 'Plugin will apply to future events'); } else { // Create scheduled task let payload: any = { name: taskName, description, plugin_name: selectedPlugin.template_slug, status: 'ACTIVE', plugin_config: selectedPlugin.config_values || {}, }; if (scheduleMode === 'onetime') { payload.schedule_type = 'ONE_TIME'; payload.run_at = `${runAtDate}T${runAtTime}:00`; } else if (scheduleMode === 'advanced') { payload.schedule_type = 'CRON'; payload.cron_expression = customCron; } 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; } else { payload.cron_expression = preset.cron_expression; } } } await axios.post('/scheduled-tasks/', payload); toast.success('Scheduled task created'); } onSuccess(); handleClose(); } catch (err: any) { setError(err.response?.data?.detail || err.response?.data?.error || 'Failed to create task'); } finally { setIsSubmitting(false); } }; if (!isOpen) return null; return (
{/* Header */}

Schedule a Task

{/* Steps indicator */}
= 1 ? 'text-blue-600 dark:text-blue-400' : 'text-gray-400'}`}>
= 1 ? 'bg-blue-600 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-500'}`}> 1
Select Plugin
= 2 ? 'text-blue-600 dark:text-blue-400' : 'text-gray-400'}`}>
= 2 ? 'bg-blue-600 text-white' : 'bg-gray-200 dark:bg-gray-700 text-gray-500'}`}> 2
Configure
{/* Content */}
{/* Step 1: Select Plugin */} {step === 1 && (

Select a plugin to schedule for automatic execution.

{pluginsLoading ? (
) : plugins.length === 0 ? (

No available plugins

Install plugins from the Marketplace first, or all your plugins are already scheduled.

) : (
{plugins.map((plugin) => ( ))}
)}
)} {/* Step 2: Configure Schedule */} {step === 2 && selectedPlugin && (
{/* Selected Plugin */}

Selected Plugin

{selectedPlugin.template_name}

{/* Task Type Selection */}
{/* Scheduled Task Options */} {taskType === 'scheduled' && ( <> {/* 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" placeholder="e.g., Daily Customer Report" />
{/* 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: {scheduleDescriptionText}
)} {/* Event Automation Options */} {taskType === 'event' && ( <> {/* Apply to which events */}
{/* Info about timing updates */}

If an event is rescheduled, the plugin timing will automatically update.

{/* When to run */}
{TRIGGER_OPTIONS.map((opt) => ( ))}
{/* Offset selection (only for time-based triggers) */} {showOffset && (
{OFFSET_PRESETS.map((preset) => ( ))}
)} {/* Event Timing Preview */}
Runs: {eventTimingDescriptionText}
)} {/* Error */} {error && }
)}
{/* Footer */}
{step === 2 && ( )}
{step === 2 && ( )}
); }; export default CreateTaskModal;