import React, { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import axios from '../api/client'; import { X, Calendar, Clock, RotateCw, Zap, CalendarDays, ChevronDown, ChevronUp } from 'lucide-react'; import toast from 'react-hot-toast'; 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; } // 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[] = [ // Interval-based { 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 }, // Cron-based (specific times) { 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 * *' }, ]; // Event trigger options (same as EventAutomations component) interface TriggerOption { value: string; label: string; } interface OffsetPreset { value: number; label: string; } const TRIGGER_OPTIONS: TriggerOption[] = [ { value: 'before_start', label: 'Before Start' }, { value: 'at_start', label: 'At Start' }, { value: 'after_start', label: 'After Start' }, { value: 'after_end', label: 'After End' }, { value: 'on_complete', label: 'When Completed' }, { value: 'on_cancel', label: 'When Canceled' }, ]; const OFFSET_PRESETS: OffsetPreset[] = [ { value: 0, label: 'Immediately' }, { value: 5, label: '5 min' }, { value: 10, label: '10 min' }, { value: 15, label: '15 min' }, { value: 30, label: '30 min' }, { value: 60, label: '1 hour' }, ]; // 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); }; 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 getEventTimingDescription = () => { const trigger = TRIGGER_OPTIONS.find(t => t.value === selectedTrigger); if (!trigger) return 'Select timing'; if (selectedTrigger === 'on_complete') return 'When event is completed'; if (selectedTrigger === 'on_cancel') return 'When event is canceled'; if (selectedOffset === 0) { if (selectedTrigger === 'before_start') return 'At event start'; if (selectedTrigger === 'at_start') return 'At event start'; if (selectedTrigger === 'after_start') return 'At event start'; if (selectedTrigger === 'after_end') return 'At event end'; } const offsetLabel = OFFSET_PRESETS.find(o => o.value === selectedOffset)?.label || `${selectedOffset} min`; if (selectedTrigger === 'before_start') return `${offsetLabel} before event starts`; if (selectedTrigger === 'at_start' || selectedTrigger === 'after_start') return `${offsetLabel} after event starts`; if (selectedTrigger === 'after_end') return `${offsetLabel} after event ends`; return trigger.label; }; 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: {getScheduleDescription()}
)} {/* 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: {getEventTimingDescription()}
)} {/* Error */} {error && (

{error}

)}
)}
{/* Footer */}
{step === 2 && ( )}
{step === 2 && ( )}
); }; export default CreateTaskModal;