程序员阿江(Relakkes) 983d83f86b feat: enhance scheduled tasks with flexible scheduling, run now, execution logs, and edit support
- Add 7 frequency modes (every N min/hours, daily, weekdays, specific days, monthly, custom cron) with progressive disclosure UI
- Add "Run Now" button with confirmation popover and fire-and-forget API
- Add execution logs panel (TaskRunsPanel) with auto-polling and accordion behavior
- Add task edit mode with cron reverse-parsing (parseCron) to populate form
- Add server-side extractAssistantText to store meaningful AI responses instead of raw NDJSON
- Fix session linking: pass --session-id to CLI subprocess so "View conversation" navigates to actual content
- Fix MACRO undefined error by adding --preload to Bun.spawn
- Add confirmation popovers for all destructive actions (run/disable/delete)
- Add DayOfWeekPicker component for specific-days scheduling
- Add cronDescribe utility with i18n support and unit tests
- Display task creation time and last run time
- Add ~50 i18n keys (en/zh) for all new UI elements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 17:40:13 +08:00

47 lines
1.5 KiB
TypeScript

import { useState } from 'react'
import type { CronTask } from '../../types/task'
import { TaskRow } from './TaskRow'
import { useTranslation } from '../../i18n'
type Props = {
tasks: CronTask[]
}
export function TaskList({ tasks }: Props) {
const t = useTranslation()
const enabledCount = tasks.filter((task) => task.enabled).length
const [expandedLogsId, setExpandedLogsId] = useState<string | null>(null)
return (
<div>
{/* Stats */}
<div className="grid grid-cols-3 gap-4 mb-6">
<StatCard label={t('tasks.totalTasks')} value={String(tasks.length)} />
<StatCard label={t('tasks.active')} value={String(enabledCount)} />
<StatCard label={t('tasks.disabled')} value={String(tasks.length - enabledCount)} />
</div>
{/* Task rows — accordion: only one logs panel open at a time */}
<div className="flex flex-col">
{tasks.map((task) => (
<TaskRow
key={task.id}
task={task}
showLogs={expandedLogsId === task.id}
onToggleLogs={() => setExpandedLogsId(expandedLogsId === task.id ? null : task.id)}
/>
))}
</div>
</div>
)
}
function StatCard({ label, value }: { label: string; value: string }) {
return (
<div className="px-4 py-3 rounded-[var(--radius-lg)] bg-[var(--color-surface-info)]">
<div className="text-2xl font-bold text-[var(--color-text-primary)]">{value}</div>
<div className="text-xs text-[var(--color-text-secondary)]">{label}</div>
</div>
)
}