import React, { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Switch } from '@/components/ui/switch'; import { Badge } from '@/components/ui/badge'; import { Trash, Plus, Calendar, Clock, RotateCcw } from 'lucide-react'; import { ScheduleFrequency, Weekday, scheduleFrequencies, weekdays, InsertScheduleRule } from '@shared/schema'; interface CronJobBuilderProps { schedules: ScheduleItem[]; onSchedulesChange: (schedules: ScheduleItem[]) => void; ruleId?: number; } export interface ScheduleItem { id?: number; ruleId?: number; name: string; description: string; frequency: ScheduleFrequency; cronExpression: string; dayOfWeek?: Weekday | null; dayOfMonth?: number | null; hour: number; minute: number; enabled: boolean; } const initialSchedule: ScheduleItem = { name: 'Default Schedule', description: 'Runs daily at midnight', frequency: 'daily', cronExpression: '0 0 * * *', hour: 0, minute: 0, enabled: true }; export const CronJobBuilder: React.FC = ({ schedules = [], onSchedulesChange, ruleId }) => { const [activeSchedules, setActiveSchedules] = useState( schedules.length > 0 ? schedules : [initialSchedule] ); const [activeTab, setActiveTab] = useState('0'); useEffect(() => { if (schedules.length > 0 && activeSchedules.length === 0) { setActiveSchedules(schedules); } }, [schedules]); const updateSchedule = (index: number, updatedSchedule: Partial) => { const newSchedules = [...activeSchedules]; newSchedules[index] = { ...newSchedules[index], ...updatedSchedule }; // Update cron expression const schedule = newSchedules[index]; let cronExpression = ''; switch (schedule.frequency) { case 'minutely': cronExpression = '* * * * *'; break; case 'hourly': cronExpression = `${schedule.minute} * * * *`; break; case 'daily': cronExpression = `${schedule.minute} ${schedule.hour} * * *`; break; case 'weekly': const dayNumber = schedule.dayOfWeek ? weekdays.indexOf(schedule.dayOfWeek) : 0; cronExpression = `${schedule.minute} ${schedule.hour} * * ${dayNumber}`; break; case 'monthly': const day = schedule.dayOfMonth || 1; cronExpression = `${schedule.minute} ${schedule.hour} ${day} * *`; break; } newSchedules[index].cronExpression = cronExpression; setActiveSchedules(newSchedules); onSchedulesChange(newSchedules); }; const addNewSchedule = () => { const newSchedule: ScheduleItem = { ...initialSchedule, name: `Schedule ${activeSchedules.length + 1}`, ruleId }; const newSchedules = [...activeSchedules, newSchedule]; setActiveSchedules(newSchedules); setActiveTab(String(newSchedules.length - 1)); onSchedulesChange(newSchedules); }; const removeSchedule = (index: number) => { if (activeSchedules.length <= 1) { return; // Don't remove the last schedule } const newSchedules = activeSchedules.filter((_, i) => i !== index); setActiveSchedules(newSchedules); // Set active tab to the previous one or the first one if (Number(activeTab) >= newSchedules.length) { setActiveTab(String(Math.max(0, newSchedules.length - 1))); } onSchedulesChange(newSchedules); }; const toggleScheduleStatus = (index: number) => { const newSchedules = [...activeSchedules]; newSchedules[index].enabled = !newSchedules[index].enabled; setActiveSchedules(newSchedules); onSchedulesChange(newSchedules); }; // Function to generate a human-readable description of the schedule const getScheduleDescription = (schedule: ScheduleItem): string => { switch (schedule.frequency) { case 'minutely': return 'Runs every minute'; case 'hourly': return `Runs every hour at ${schedule.minute} minutes past the hour`; case 'daily': return `Runs daily at ${formatTime(schedule.hour, schedule.minute)}`; case 'weekly': return `Runs weekly on ${schedule.dayOfWeek || 'Monday'} at ${formatTime(schedule.hour, schedule.minute)}`; case 'monthly': return `Runs monthly on day ${schedule.dayOfMonth || 1} at ${formatTime(schedule.hour, schedule.minute)}`; default: return 'Custom schedule'; } }; // Helper to format time in 12-hour format const formatTime = (hour: number, minute: number): string => { const period = hour >= 12 ? 'PM' : 'AM'; const displayHour = hour % 12 || 12; const displayMinute = minute.toString().padStart(2, '0'); return `${displayHour}:${displayMinute} ${period}`; }; return ( Schedule Configuration
{activeSchedules.map((schedule, index) => ( {schedule.name} {!schedule.enabled && ( Disabled )} ))}
{activeSchedules.map((schedule, index) => (
updateSchedule(index, { name: e.target.value })} placeholder="Schedule Name" />
{(schedule.frequency === 'weekly') && (
)} {(schedule.frequency === 'monthly') && (
)} {(schedule.frequency !== 'minutely') && (
{(schedule.frequency !== 'hourly') && (
)}
)}
updateSchedule(index, { description: e.target.value })} placeholder="Schedule Description" />
{schedule.cronExpression}

Schedule Summary:

{getScheduleDescription(schedule)}

toggleScheduleStatus(index)} />
{activeSchedules.length > 1 && ( )}
))}
); }; export default CronJobBuilder;