/** * AutomationPanel - Unified Automation Entry Point * * Combines Hands and Workflows into a single unified view, * with category filtering, batch operations, and scheduling. * * @module components/Automation/AutomationPanel */ import { useState, useEffect, useCallback, useMemo } from 'react'; import { useHandStore } from '../../store/handStore'; import { useWorkflowStore } from '../../store/workflowStore'; import { type AutomationItem, type CategoryType, type CategoryStats, adaptToAutomationItems, calculateCategoryStats, filterByCategory, searchAutomationItems, } from '../../types/automation'; import { AutomationCard } from './AutomationCard'; import { AutomationFilters } from './AutomationFilters'; import { BatchActionBar } from './BatchActionBar'; import { Zap, RefreshCw, Plus, Calendar, Search, X, } from 'lucide-react'; import { useToast } from '../ui/Toast'; // === View Mode === type ViewMode = 'grid' | 'list'; // === Component Props === interface AutomationPanelProps { initialCategory?: CategoryType; onSelect?: (item: AutomationItem) => void; showBatchActions?: boolean; } // === Main Component === export function AutomationPanel({ initialCategory = 'all', onSelect, showBatchActions = true, }: AutomationPanelProps) { // Store state - use domain stores const hands = useHandStore((s) => s.hands); const workflows = useWorkflowStore((s) => s.workflows); const handLoading = useHandStore((s) => s.isLoading); const workflowLoading = useWorkflowStore((s) => s.isLoading); const isLoading = handLoading || workflowLoading; const loadHands = useHandStore((s) => s.loadHands); const loadWorkflows = useWorkflowStore((s) => s.loadWorkflows); const triggerHand = useHandStore((s) => s.triggerHand); const triggerWorkflow = useWorkflowStore((s) => s.triggerWorkflow); // UI state const [selectedCategory, setSelectedCategory] = useState(initialCategory); const [searchQuery, setSearchQuery] = useState(''); const [viewMode, setViewMode] = useState('grid'); const [selectedIds, setSelectedIds] = useState>(new Set()); const [executingIds, setExecutingIds] = useState>(new Set()); const [showWorkflowDialog, setShowWorkflowDialog] = useState(false); const [showSchedulerDialog, setShowSchedulerDialog] = useState(false); const { toast } = useToast(); // Load data on mount useEffect(() => { loadHands(); loadWorkflows(); }, [loadHands, loadWorkflows]); // Adapt hands and workflows to automation items const automationItems = useMemo(() => { return adaptToAutomationItems(hands, workflows); }, [hands, workflows]); // Calculate category stats const categoryStats = useMemo(() => { return calculateCategoryStats(automationItems); }, [automationItems]); // Filter and search items const filteredItems = useMemo(() => { let items = filterByCategory(automationItems, selectedCategory); if (searchQuery.trim()) { items = searchAutomationItems(items, searchQuery); } return items; }, [automationItems, selectedCategory, searchQuery]); // Selection handlers const handleSelect = useCallback((id: string, selected: boolean) => { setSelectedIds(prev => { const next = new Set(prev); if (selected) { next.add(id); } else { next.delete(id); } return next; }); }, []); const handleSelectAll = useCallback(() => { setSelectedIds(new Set(filteredItems.map(item => item.id))); }, [filteredItems]); const handleDeselectAll = useCallback(() => { setSelectedIds(new Set()); }, []); // Workflow dialog handlers const handleCreateWorkflow = useCallback(() => { setShowWorkflowDialog(true); }, []); const handleSchedulerManage = useCallback(() => { setShowSchedulerDialog(true); }, []); // Execute handler const handleExecute = useCallback(async (item: AutomationItem, params?: Record) => { setExecutingIds(prev => new Set(prev).add(item.id)); try { if (item.type === 'hand') { await triggerHand(item.id, params); } else { await triggerWorkflow(item.id, params); } toast(`${item.name} 执行成功`, 'success'); } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err); toast(`${item.name} 执行失败: ${errorMsg}`, 'error'); } finally { setExecutingIds(prev => { const next = new Set(prev); next.delete(item.id); return next; }); } }, [triggerHand, triggerWorkflow, toast]); // Batch execute const handleBatchExecute = useCallback(async () => { const itemsToExecute = filteredItems.filter(item => selectedIds.has(item.id)); let successCount = 0; let failCount = 0; for (const item of itemsToExecute) { try { if (item.type === 'hand') { await triggerHand(item.id); } else { await triggerWorkflow(item.id); } successCount++; } catch { failCount++; } } if (successCount > 0) { toast(`成功执行 ${successCount} 个项目`, 'success'); } if (failCount > 0) { toast(`${failCount} 个项目执行失败`, 'error'); } setSelectedIds(new Set()); }, [filteredItems, selectedIds, triggerHand, triggerWorkflow, toast]); // Refresh handler const handleRefresh = useCallback(async () => { await Promise.all([loadHands(), loadWorkflows()]); toast('数据已刷新', 'success'); }, [loadHands, loadWorkflows, toast]); return (
{/* Header */}

自动化

({automationItems.length})
{/* Filters */} {/* Content */}
{isLoading && automationItems.length === 0 ? (
) : filteredItems.length === 0 ? (

{searchQuery ? '没有找到匹配的项目' : '暂无自动化项目'}

) : (
{filteredItems.map(item => ( handleSelect(item.id, selected)} onExecute={(params) => handleExecute(item, params)} onClick={() => onSelect?.(item)} /> ))}
)}
{/* Batch Actions */} {showBatchActions && selectedIds.size > 0 && ( { toast('批量调度功能开发中', 'info'); }} /> )} {/* Create Workflow Dialog */} {showWorkflowDialog && (

新建工作流