import React, { useCallback, useEffect, useState } from 'react'; import { Button } from '@renderer/components/ui/button'; import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover'; import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip'; import { useStore } from '@renderer/store'; import { formatNextRun, getCronDescription } from '@renderer/utils/scheduleFormatters'; import { ChevronDown, ChevronRight, MoreHorizontal, Pause, Pencil, Play, Plus, Trash2, Zap, } from 'lucide-react'; import { useShallow } from 'zustand/react/shallow'; import { LaunchTeamDialog } from '../dialogs/LaunchTeamDialog'; import { ScheduleEmptyState } from './ScheduleEmptyState'; import { ScheduleRunLogDialog } from './ScheduleRunLogDialog'; import { ScheduleRunRow } from './ScheduleRunRow'; import { ScheduleStatusBadge } from './ScheduleStatusBadge'; import type { Schedule, ScheduleRun } from '@shared/types'; // ============================================================================= // Props // ============================================================================= interface ScheduleSectionProps { teamName: string; } // ============================================================================= // ScheduleRow // ============================================================================= interface ScheduleRowProps { schedule: Schedule; onEdit: (schedule: Schedule) => void; onDelete: (id: string) => void; onPause: (id: string) => void; onResume: (id: string) => void; onTriggerNow: (id: string) => void; } const ScheduleRow = ({ schedule, onEdit, onDelete, onPause, onResume, onTriggerNow, }: ScheduleRowProps): React.JSX.Element => { const [expanded, setExpanded] = useState(false); const [selectedRun, setSelectedRun] = useState(null); const runs = useStore(useShallow((s) => s.scheduleRuns[schedule.id] ?? [])); const runsLoading = useStore((s) => s.scheduleRunsLoading[schedule.id] ?? false); const fetchRunHistory = useStore((s) => s.fetchRunHistory); const handleExpand = useCallback(() => { const next = !expanded; setExpanded(next); if (next && runs.length === 0 && !runsLoading) { void fetchRunHistory(schedule.id); } }, [expanded, runs.length, runsLoading, fetchRunHistory, schedule.id]); return (
{/* Header row */}
{/* Expand toggle */} {/* Info */}
{schedule.label || getCronDescription(schedule.cronExpression)}
{schedule.label ? {getCronDescription(schedule.cronExpression)} : null} Next: {formatNextRun(schedule.nextRunAt)} {schedule.nextRunAt ? ( {new Date(schedule.nextRunAt).toLocaleString()} ) : null} {schedule.timezone}
{/* Actions */}
Run now {schedule.status === 'active' ? ( ) : ( )}
{/* Expanded: Run history */} {expanded ? (
{runsLoading ? (
Loading run history...
) : runs.length === 0 ? (
No runs yet
) : (
{runs.slice(0, 10).map((run) => ( ))}
)}
) : null} {/* Run Log Dialog */} setSelectedRun(null)} />
); }; // ============================================================================= // ScheduleSection // ============================================================================= export const ScheduleSection = ({ teamName }: ScheduleSectionProps): React.JSX.Element => { const { schedules, pauseSchedule, resumeSchedule, deleteSchedule, triggerNow, fetchSchedules } = useStore( useShallow((s) => ({ schedules: s.schedules.filter((sch) => sch.teamName === teamName), pauseSchedule: s.pauseSchedule, resumeSchedule: s.resumeSchedule, deleteSchedule: s.deleteSchedule, triggerNow: s.triggerNow, fetchSchedules: s.fetchSchedules, })) ); const [dialogOpen, setDialogOpen] = useState(false); const [editingSchedule, setEditingSchedule] = useState(null); // Fetch schedules on mount useEffect(() => { void fetchSchedules(); }, [fetchSchedules]); const handleEdit = useCallback((schedule: Schedule) => { setEditingSchedule(schedule); setDialogOpen(true); }, []); const handleCreate = useCallback(() => { setEditingSchedule(null); setDialogOpen(true); }, []); const handleClose = useCallback(() => { setDialogOpen(false); setEditingSchedule(null); }, []); const handleDelete = useCallback( async (id: string) => { try { await deleteSchedule(id); } catch (err) { console.error('Failed to delete schedule:', err); } }, [deleteSchedule] ); const handleTriggerNow = useCallback( async (id: string) => { try { await triggerNow(id); } catch (err) { console.error('Failed to trigger schedule:', err); } }, [triggerNow] ); return (
{/* Header with create button */}
{schedules.length > 0 ? `${schedules.length} schedule${schedules.length > 1 ? 's' : ''}` : ''}
{/* Schedule list or empty state */} {schedules.length === 0 ? ( ) : (
{schedules.map((schedule) => ( void handleDelete(id)} onPause={(id) => void pauseSchedule(id)} onResume={(id) => void resumeSchedule(id)} onTriggerNow={(id) => void handleTriggerNow(id)} /> ))}
)} {/* Create/Edit Dialog */}
); };