Files
MusicWorkshop/frontend/src/components/settings/ScheduleSection.jsx
T
liumangmang 7d003ff822 refactor: rewrite SettingsPage as lightweight container with hooks and components
Extracted cron utilities to utils/schedule.js, all state management
(15+ useState) to useSettingsForm hook, and 5 config sections + 2 dialogs
+ 1 status badge into focused components. SettingsPage reduced from
~700 lines to ~130 line container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 12:06:26 +08:00

110 lines
6.1 KiB
React

// frontend/src/components/settings/ScheduleSection.jsx
import { CalendarClock, Clock, PlayCircle } from 'lucide-react';
import {
SCHEDULE_TYPE, DEFAULT_CRON, DEFAULT_TIME,
getScheduleSummary, buildDailyCron, buildWeeklyCron
} from '../../utils/schedule';
export default function ScheduleSection({ schedule = {}, onUpdate }) {
const enabled = schedule.enabled !== false;
const type = schedule.type || SCHEDULE_TYPE.DAILY;
const time = schedule.time || DEFAULT_TIME;
const dayOfWeek = schedule.dayOfWeek || '1';
const cron = schedule.cron || DEFAULT_CRON;
const setEnabled = (val) => onUpdate('schedule', { ...schedule, enabled: val });
const setType = (val) => {
if (val === SCHEDULE_TYPE.DAILY) {
onUpdate('schedule', { ...schedule, type: val, cron: buildDailyCron(time) });
} else if (val === SCHEDULE_TYPE.WEEKLY) {
onUpdate('schedule', { ...schedule, type: val, dayOfWeek, cron: buildWeeklyCron(dayOfWeek, time) });
} else {
onUpdate('schedule', { ...schedule, type: val, cron: schedule.cron || DEFAULT_CRON });
}
};
const setTime = (val) => {
const newCron = type === SCHEDULE_TYPE.WEEKLY
? buildWeeklyCron(dayOfWeek, val) : buildDailyCron(val);
onUpdate('schedule', { ...schedule, type, time: val, cron: newCron });
};
const setDay = (val) => {
onUpdate('schedule', { ...schedule, type, dayOfWeek: val, cron: buildWeeklyCron(val, time) });
};
const setCron = (val) => {
onUpdate('schedule', { ...schedule, type: SCHEDULE_TYPE.CRON, cron: val });
};
return (
<div className="bg-slate-900 border border-slate-800 rounded-xl p-6 shadow-lg">
<div className="flex justify-between items-center mb-5 border-b border-slate-800 pb-3">
<h3 className="text-base font-semibold text-white flex items-center">
<CalendarClock className="w-5 h-5 mr-2 text-amber-400" />
自动化定时任务
</h3>
<label className="flex items-center cursor-pointer">
<div className="relative">
<input type="checkbox" className="sr-only" checked={enabled} onChange={(e) => setEnabled(e.target.checked)} />
<div className={`block w-10 h-6 rounded-full transition-colors ${enabled ? 'bg-amber-500' : 'bg-slate-700'}`}></div>
<div className={`dot absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition-transform ${enabled ? 'transform translate-x-4' : ''}`}></div>
</div>
<span className="ml-3 text-sm font-medium text-slate-300">{enabled ? '已启用自动入库' : '已暂停自动入库'}</span>
</label>
</div>
<div className={`flex flex-col gap-4 transition-opacity ${enabled ? 'opacity-100' : 'opacity-40 pointer-events-none'}`}>
<div className="flex flex-wrap gap-4 items-end">
<div className="w-44 shrink-0">
<label className="flex items-center text-xs font-medium text-slate-400 mb-1.5">执行频率</label>
<select className="w-full px-3 py-2 rounded bg-slate-950 border border-slate-700 text-slate-200 text-sm focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
value={type} onChange={(e) => setType(e.target.value)}>
<option value={SCHEDULE_TYPE.DAILY}>每天执行</option>
<option value={SCHEDULE_TYPE.WEEKLY}>每周执行</option>
<option value={SCHEDULE_TYPE.CRON}>专家模式 (Cron)</option>
</select>
</div>
{type === SCHEDULE_TYPE.WEEKLY && (
<div className="w-32 shrink-0">
<label className="flex items-center text-xs font-medium text-slate-400 mb-1.5">星期</label>
<select className="w-full px-3 py-2 rounded bg-slate-950 border border-slate-700 text-slate-200 text-sm focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
value={dayOfWeek} onChange={(e) => setDay(e.target.value)}>
<option value="1">星期一</option><option value="2">星期二</option>
<option value="3">星期三</option><option value="4">星期四</option>
<option value="5">星期五</option><option value="6">星期六</option>
<option value="0">星期日</option>
</select>
</div>
)}
{type !== SCHEDULE_TYPE.CRON && (
<div className="w-32 shrink-0">
<label className="flex items-center text-xs font-medium text-slate-400 mb-1.5">具体时间</label>
<input type="time" className="w-full px-3 py-2 rounded bg-slate-950 border border-slate-700 text-slate-200 text-sm font-mono focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
style={{ colorScheme: 'dark' }} value={time} onChange={(e) => setTime(e.target.value)} />
</div>
)}
{type === SCHEDULE_TYPE.CRON && (
<div className="flex-1 min-w-[200px]">
<label className="flex items-center text-xs font-medium text-slate-400 mb-1.5">Cron 表达式</label>
<input type="text" className="w-full px-3 py-2 rounded bg-slate-950 border border-slate-700 text-amber-400 text-sm font-mono focus:ring-amber-500 focus:border-amber-500 focus:outline-none"
value={cron} onChange={(e) => setCron(e.target.value)} placeholder="例如: 0 2 * * *" />
</div>
)}
</div>
<div className="flex items-center justify-between gap-4 rounded-xl border border-slate-800/90 bg-[#0b1328] px-4 py-3.5">
<div className="flex min-w-0 items-center text-sm">
<Clock className="mr-2 h-4 w-4 shrink-0 text-amber-400" />
<span className="mr-2 shrink-0 text-slate-200">解析结果:</span>
<span className="truncate font-medium text-amber-400">{getScheduleSummary(schedule)}</span>
</div>
<button className="flex shrink-0 items-center justify-center rounded-lg border border-slate-600 bg-slate-700/70 px-4 py-2 text-sm font-medium text-slate-100 shadow-inner shadow-slate-950/30 transition hover:bg-slate-600/80">
<PlayCircle className="mr-1.5 h-4 w-4" /> 立即运行测试
</button>
</div>
</div>
</div>
);
}