/** * ScheduleEditor - Visual Schedule Configuration * * Provides a visual interface for configuring schedules * without requiring knowledge of cron syntax. * * @module components/Automation/ScheduleEditor */ import { useState, useCallback, useMemo } from 'react'; import type { ScheduleInfo } from '../../types/automation'; import { Calendar, Info, } from 'lucide-react'; import { useToast } from '../ui/Toast'; // === Frequency Types === type Frequency = 'once' | 'daily' | 'weekly' | 'monthly' | 'custom'; // === Timezones === const COMMON_TIMEZONES = [ { value: 'Asia/Shanghai', label: '北京时间 (UTC+8)' }, { value: 'Asia/Tokyo', label: '东京时间 (UTC+9)' }, { value: 'Asia/Singapore', label: '新加坡时间 (UTC+8)' }, { value: 'America/New_York', label: '纽约时间 (UTC-5)' }, { value: 'America/Los_Angeles', label: '洛杉矶时间 (UTC-8)' }, { value: 'Europe/London', label: '伦敦时间 (UTC+0)' }, { value: 'UTC', label: '协调世界时 (UTC)' }, ]; // === Day Names === const DAY_NAMES = ['日', '一', '二', '三', '四', '五', '六']; const DAY_NAMES_FULL = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; // === Component Props === interface ScheduleEditorProps { schedule?: ScheduleInfo; onSave: (schedule: ScheduleInfo) => void; onCancel: () => void; itemName?: string; } // === Helper Functions === function formatSchedulePreview(schedule: ScheduleInfo): string { const { frequency, time, daysOfWeek, dayOfMonth, timezone } = schedule; const timeStr = `${time.hour.toString().padStart(2, '0')}:${time.minute.toString().padStart(2, '0')}`; const tzLabel = COMMON_TIMEZONES.find(tz => tz.value === timezone)?.label || timezone; switch (frequency) { case 'once': return `一次性执行于 ${timeStr} (${tzLabel})`; case 'daily': return `每天 ${timeStr} (${tzLabel})`; case 'weekly': const days = (daysOfWeek || []).map(d => DAY_NAMES_FULL[d]).join('、'); return `每${days} ${timeStr} (${tzLabel})`; case 'monthly': return `每月${dayOfMonth || 1}日 ${timeStr} (${tzLabel})`; case 'custom': return schedule.customCron || '自定义调度'; default: return '未设置'; } } // === Main Component === export function ScheduleEditor({ schedule, onSave, onCancel, itemName = '自动化项目', }: ScheduleEditorProps) { const { toast } = useToast(); // Initialize state from existing schedule const [frequency, setFrequency] = useState(schedule?.frequency || 'daily'); const [time, setTime] = useState(schedule?.time || { hour: 9, minute: 0 }); const [daysOfWeek, setDaysOfWeek] = useState(schedule?.daysOfWeek || [1, 2, 3, 4, 5]); const [dayOfMonth, setDayOfMonth] = useState(schedule?.dayOfMonth || 1); const [timezone, setTimezone] = useState(schedule?.timezone || 'Asia/Shanghai'); const [endDate, setEndDate] = useState(schedule?.endDate || ''); const [customCron, setCustomCron] = useState(schedule?.customCron || ''); const [enabled, setEnabled] = useState(schedule?.enabled ?? true); // Toggle day of week const toggleDayOfWeek = useCallback((day: number) => { setDaysOfWeek(prev => prev.includes(day) ? prev.filter(d => d !== day) : [...prev, day].sort() ); }, []); // Handle save const handleSave = useCallback(() => { // Validate if (frequency === 'weekly' && daysOfWeek.length === 0) { toast('请选择至少一个重复日期', 'error'); return; } if (frequency === 'custom' && !customCron) { toast('请输入自定义 cron 表达式', 'error'); return; } const newSchedule: ScheduleInfo = { enabled, frequency, time, daysOfWeek: frequency === 'weekly' ? daysOfWeek : undefined, dayOfMonth: frequency === 'monthly' ? dayOfMonth : undefined, customCron: frequency === 'custom' ? customCron : undefined, timezone, endDate: endDate || undefined, }; onSave(newSchedule); toast('调度设置已保存', 'success'); }, [frequency, daysOfWeek, customCron, enabled, time, dayOfMonth, timezone, endDate, onSave, toast]); // Generate preview const preview = useMemo(() => { return formatSchedulePreview({ enabled, frequency, time, daysOfWeek, dayOfMonth, customCron, timezone, }); }, [enabled, frequency, time, daysOfWeek, dayOfMonth, customCron, timezone]); return (
{/* Backdrop */}
{/* Modal */}
{/* Header */}

调度设置

{itemName && (

{itemName}

)}
{/* Body */}
{/* Enable Toggle */}

启用调度

开启后,此项目将按照设定的时间自动执行

{/* Frequency Selection */}
{[ { value: 'once', label: '一次' }, { value: 'daily', label: '每天' }, { value: 'weekly', label: '每周' }, { value: 'monthly', label: '每月' }, { value: 'custom', label: '自定义' }, ].map(option => ( ))}
{/* Time Selection */} {frequency !== 'custom' && (
setTime(prev => ({ ...prev, hour: parseInt(e.target.value) || 0 }))} className="w-16 px-3 py-2 text-center border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white" /> : setTime(prev => ({ ...prev, minute: parseInt(e.target.value) || 0 }))} className="w-16 px-3 py-2 text-center border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white" />
)} {/* Weekly Days Selection */} {frequency === 'weekly' && (
{DAY_NAMES.map((day, index) => ( ))}
)} {/* Monthly Day Selection */} {frequency === 'monthly' && (
)} {/* Custom Cron Input */} {frequency === 'custom' && (
setCustomCron(e.target.value)} placeholder="* * * * * (分 时 日 月 周)" className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white font-mono text-sm" />

示例: "0 9 * * *" 表示每天 9:00 执行

)} {/* End Date */} {frequency !== 'once' && (
setEndDate(e.target.value)} className="w-full px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white" />
)} {/* Preview */}

预览

{preview}

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