import { Card, CardBody, CardHeader, Input, Tooltip, useDisclosure } from '@heroui/react' import { Button, Chip } from '@heroui/react' import { ask } from '@tauri-apps/plugin-dialog' import { platform } from '@tauri-apps/plugin-os' import CronExpressionParser from 'cron-parser' import cronstrue from 'cronstrue' import { formatDistance } from 'date-fns' import { AlertCircleIcon, Clock7Icon, PauseIcon, PlayIcon, Trash2Icon } from 'lucide-react' import { useCallback, useEffect, useMemo, useState } from 'react' import { buildReadablePath } from '../../lib/format' import { useHostStore } from '../../store/host' import type { ScheduledTask } from '../../types/schedules' import CommandsDropdown from '../components/CommandsDropdown' import ScheduleEditDrawer from '../components/ScheduleEditDrawer' export default function Schedules() { const scheduledTasks = useHostStore((state) => state.scheduledTasks) const [selectedTask, setSelectedTask] = useState(null) const { isOpen, onOpen, onClose } = useDisclosure() const handleOpenDrawer = useCallback( (task: ScheduledTask) => { setSelectedTask(task) onOpen() }, [onOpen] ) if (scheduledTasks.length === 0) { return (

You can schedule tasks to run later, when the UI is in the background.

) } return (
{platform() === 'macos' && (
)} {scheduledTasks.map((task) => ( ))} {selectedTask && ( )}
) } function TaskCard({ task, onOpenDrawer, }: { task: ScheduledTask; onOpenDrawer: (task: ScheduledTask) => void }) { const [isBusy, setIsBusy] = useState(false) const [isEditingName, setIsEditingName] = useState(false) const [editingName, setEditingName] = useState(task.name) const removeScheduledTask = useHostStore((state) => state.removeScheduledTask) const updateScheduledTask = useHostStore((state) => state.updateScheduledTask) useEffect(() => { if (!isEditingName) { setEditingName(task.name) } }, [task.name, isEditingName]) const nextRun = useMemo(() => { const parsed = CronExpressionParser.parse(task.cron) return parsed.hasNext() ? parsed.next().toDate() : null }, [task.cron]) const source = useMemo( () => ('source' in task.args ? task.args.source : task.args.sources[0]), [task.args] ) const nextRunLabel = useMemo(() => { if (nextRun) { const distance = formatDistance(nextRun, new Date(), { addSuffix: true }) return distance.charAt(0).toUpperCase() + distance.slice(1) } return 'Never' }, [nextRun]) const lastRunLabel = useMemo(() => { if (task.isRunning) { return 'Running now' } if (task.lastRun) { const distance = formatDistance(new Date(task.lastRun), new Date(), { addSuffix: true, }) return distance.charAt(0).toUpperCase() + distance.slice(1) } return 'Never' }, [task.isRunning, task.lastRun]) return ( onOpenDrawer(task)} style={{ flexShrink: 0, // border: '1px solid #e0e0e070', // borderBottom: '1px solid #e0e0e070', // padding: '0.5rem', }} className="p-2 border-b border-divider" >
{task.operation.toUpperCase()}
{ setEditingName(task.name || 'Untitled Schedule') setIsEditingName(true) e.currentTarget.select() }} onBlur={() => { updateScheduledTask(task.id, { name: editingName }) setIsEditingName(false) }} onKeyDown={(e) => { if (e.key === 'Enter') { updateScheduledTask(task.id, { name: editingName }) setIsEditingName(false) e.currentTarget.blur() } else if (e.key === 'Escape') { setEditingName(task.name) setIsEditingName(false) e.currentTarget.blur() } }} onValueChange={(newName) => setEditingName(newName)} />
{buildReadablePath(source, 'short')} {'→'}{' '} {'destination' in task.args ? buildReadablePath(task.args.destination, 'short') : 'N/A'}
{lastRunLabel}

Last run

{nextRunLabel}

Next run

{task.lastRunError ? ( <>

{task.lastRunError}

) : ( <>

{cronstrue.toString(task.cron)}.

)}
) }