import { useState, useEffect, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { IoIosTrash, IoIosPlay } from 'react-icons/io'; import { MdModeEdit } from 'react-icons/md'; import { useWorkflows, useWorkflowOperations, Workflow } from '../../hooks/useWorkflows'; import { useApiRequest } from '../../hooks/useApi'; import { useLanguage } from '../../contexts/LanguageContext'; import type { EditFieldConfig } from '../Popup/EditForm'; import type { WorkflowsLogicReturn, WorkflowMessageCounts, WorkflowActionConfig, WorkflowColumnConfig } from './workflowsTypes'; import styles from './WorkflowsTable.module.css'; export function useWorkflowsLogic(): WorkflowsLogicReturn { const { workflows, loading, error, refetch } = useWorkflows(); const navigate = useNavigate(); const { t } = useLanguage(); // State to track message counts for each workflow const [workflowMessageCounts, setWorkflowMessageCounts] = useState({}); const { request } = useApiRequest(); // Debug: Log workflow data to see the actual structure console.log('Workflows data:', workflows); if (workflows && workflows.length > 0) { const firstWorkflow = workflows[0]; console.log('First workflow object:', firstWorkflow); console.log('First workflow keys:', Object.keys(firstWorkflow)); console.log('First workflow stats:', firstWorkflow.stats); if (firstWorkflow.stats) { console.log('Stats keys:', Object.keys(firstWorkflow.stats)); console.log('Stats object:', firstWorkflow.stats); } console.log('First workflow messages array:', firstWorkflow.messages, 'length:', firstWorkflow.messages?.length); } const { deleteWorkflow, updateWorkflow, deletingWorkflows } = useWorkflowOperations(); // Edit modal state const [editModalOpen, setEditModalOpen] = useState(false); const [editingWorkflow, setEditingWorkflow] = useState(null); // Function to fetch message count for a single workflow const fetchMessageCount = async (workflowId: string) => { try { console.log(`Fetching messages for workflow: ${workflowId}`); const messages = await request({ url: `/api/workflows/${workflowId}/messages`, method: 'get' }); console.log(`Messages for ${workflowId}:`, messages, 'length:', messages?.length); const count = Array.isArray(messages) ? messages.length : 0; console.log(`Setting message count for ${workflowId}:`, count); setWorkflowMessageCounts(prev => ({ ...prev, [workflowId]: count })); } catch (error) { console.error(`Failed to fetch message count for workflow ${workflowId}:`, error); // Set count to 0 for failed requests setWorkflowMessageCounts(prev => ({ ...prev, [workflowId]: 0 })); } }; // Effect to fetch message counts when workflows change useEffect(() => { if (workflows && workflows.length > 0) { workflows.forEach(workflow => { // Only fetch if we don't already have the count if (!(workflow.id in workflowMessageCounts)) { fetchMessageCount(workflow.id); } }); } }, [workflows]); // Don't include workflowMessageCounts to avoid infinite loop // Configure edit fields for workflow name editing const editWorkflowFields: EditFieldConfig[] = useMemo(() => [ { key: 'name', label: t('workflows.field.name', 'Workflow Name'), type: 'string', editable: true, required: true, validator: (value: string) => { if (!value || value.trim() === '') { return t('workflows.validation.nameRequired', 'Workflow name cannot be empty'); } if (value.length > 100) { return t('workflows.validation.nameTooLong', 'Workflow name cannot exceed 100 characters'); } return null; } } ], [t]); // Configure columns for the workflows table const columns: WorkflowColumnConfig[] = useMemo(() => [ { key: 'id', label: t('workflows.column.id'), type: 'string', width: 180, minWidth: 150, maxWidth: 250, sortable: true, filterable: true, searchable: true, formatter: (value: string) => ( {value.length > 8 ? `${value.substring(0, 8)}...` : value} ) }, { key: 'name', label: t('workflows.column.name'), type: 'string', width: 200, minWidth: 150, maxWidth: 300, sortable: true, filterable: true, searchable: true, formatter: (value: string | undefined) => ( {value || t('workflows.unnamed')} ) }, { key: 'status', label: t('workflows.column.status'), type: 'enum', width: 120, minWidth: 100, maxWidth: 150, sortable: true, filterable: true, filterOptions: ['running', 'completed', 'failed', 'stopped', 'pending'], formatter: (value: string) => ( {t(`workflows.status.${value}`, value)} ) }, { key: 'currentRound', label: t('workflows.column.round'), type: 'number', width: 80, minWidth: 60, maxWidth: 100, sortable: true, filterable: true, formatter: (value: number | undefined) => ( {value || 1} ) }, { key: 'startedAt', label: t('workflows.column.started'), type: 'date', width: 140, minWidth: 120, maxWidth: 180, sortable: true, filterable: true, formatter: (value: number | undefined) => { if (!value) return '-'; try { // Backend sends UTC timestamp in seconds (float), convert to milliseconds for Date constructor const date = new Date(value * 1000); // Check if date is valid if (isNaN(date.getTime())) { console.warn('Invalid startedAt date:', value); return '-'; } const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); const timezoneOffset = date.getTimezoneOffset(); const offsetHours = Math.floor(Math.abs(timezoneOffset) / 60); const offsetMinutes = Math.abs(timezoneOffset) % 60; const offsetSign = timezoneOffset <= 0 ? '+' : '-'; const timezone = `GMT${offsetSign}${offsetHours}${offsetMinutes > 0 ? ':' + offsetMinutes.toString().padStart(2, '0') : ''}`; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} ${timezone}`; } catch (error) { console.warn('Error parsing startedAt date:', value, error); return '-'; } } }, { key: 'lastActivity', label: t('workflows.column.lastActivity'), type: 'date', width: 140, minWidth: 120, maxWidth: 180, sortable: true, filterable: true, formatter: (value: number | undefined) => { if (!value) return '-'; try { // Backend sends UTC timestamp in seconds (float), convert to milliseconds for Date constructor const date = new Date(value * 1000); // Check if date is valid if (isNaN(date.getTime())) { console.warn('Invalid lastActivity date:', value); return '-'; } const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); const timezoneOffset = date.getTimezoneOffset(); const offsetHours = Math.floor(Math.abs(timezoneOffset) / 60); const offsetMinutes = Math.abs(timezoneOffset) % 60; const offsetSign = timezoneOffset <= 0 ? '+' : '-'; const timezone = `GMT${offsetSign}${offsetHours}${offsetMinutes > 0 ? ':' + offsetMinutes.toString().padStart(2, '0') : ''}`; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} ${timezone}`; } catch (error) { console.warn('Error parsing lastActivity date:', value, error); return '-'; } } }, { key: 'messages', label: t('workflows.column.messages'), type: 'number', width: 100, minWidth: 80, maxWidth: 120, sortable: true, filterable: false, formatter: (_value: any, row: any) => { // Get message count from our fetched data, just like in Dashboard component const workflowId = row?.id; const messageCount = workflowId ? workflowMessageCounts[workflowId] : undefined; console.log(`Messages formatter for ${workflowId}:`, { workflowId, messageCount, hasInCache: workflowId in workflowMessageCounts, allCounts: workflowMessageCounts }); // Show the count if available, otherwise show loading indicator or dash let displayValue; if (messageCount !== undefined) { displayValue = messageCount; } else if (workflowId && workflows.some(w => w.id === workflowId)) { // We're still loading this count displayValue = '...'; } else { displayValue = '-'; } return ( {displayValue} ); } } ], [t, workflowMessageCounts, workflows]); // Handle workflow actions const handleDeleteWorkflow = async (workflow: Workflow) => { const workflowName = workflow.name || workflow.id; if (window.confirm(t('workflows.delete.confirm').replace('{name}', workflowName))) { const success = await deleteWorkflow(workflow.id); if (success) { refetch(); // Refresh the workflows list } } }; // Handle single workflow deletion for bulk delete const handleDeleteSingle = async (workflow: Workflow) => { const workflowName = workflow.name || workflow.id; if (window.confirm(t('workflows.delete.confirm', 'Are you sure you want to delete "{name}"?').replace('{name}', workflowName))) { const success = await deleteWorkflow(workflow.id); if (success) { refetch(); // Refresh the workflows list } else { console.error('Delete failed for workflow:', workflow.id); } } }; // Handle multiple workflow deletion const handleDeleteMultiple = async (workflowsToDelete: Workflow[]) => { const workflowCount = workflowsToDelete.length; if (window.confirm(t('workflows.delete.confirmMultiple', 'Are you sure you want to delete {count} workflows?').replace('{count}', workflowCount.toString()))) { // Start all delete operations simultaneously const deletePromises = workflowsToDelete.map(async (workflow) => { try { const success = await deleteWorkflow(workflow.id); return { workflowId: workflow.id, success }; } catch (error) { console.error('Failed to delete workflow:', workflow.id, error); return { workflowId: workflow.id, success: false }; } }); // Wait for all deletions to complete const results = await Promise.all(deletePromises); // Check if any deletions failed const failedDeletions = results.filter(result => !result.success); if (failedDeletions.length > 0) { console.error('Some workflow deletions failed:', failedDeletions); } // Refresh the workflow list regardless of individual failures refetch(); } }; // Handle edit workflow const handleEditWorkflow = (workflow: Workflow) => { setEditingWorkflow(workflow); setEditModalOpen(true); }; // Handle save workflow const handleSaveWorkflow = async (updatedWorkflow: Workflow) => { if (!editingWorkflow) return; try { // Call API to update workflow name const result = await updateWorkflow(editingWorkflow.id, { name: updatedWorkflow.name }); if (result.success) { // Close modal setEditModalOpen(false); setEditingWorkflow(null); // Refresh workflow list await refetch(); // Notify other components that workflows have been updated window.dispatchEvent(new CustomEvent('workflowUpdated', { detail: { workflowId: editingWorkflow.id, newName: updatedWorkflow.name } })); } else { console.error('Failed to update workflow:', result.error); // TODO: Show error message to user } } catch (error) { console.error('Failed to update workflow:', error); // TODO: Show error message to user } }; // Handle cancel edit const handleCancelEdit = () => { setEditModalOpen(false); setEditingWorkflow(null); }; // Handle play workflow - navigate to dashboard with workflow ID const handlePlayWorkflow = (workflow: Workflow) => { // Navigate to dashboard with workflow ID as URL parameter navigate(`/dashboard?workflowId=${workflow.id}`); }; // Configure action buttons const actions: WorkflowActionConfig[] = useMemo(() => [ { label: t('workflows.action.play'), icon: (_row: Workflow) => { return ; }, onClick: (row: Workflow) => { handlePlayWorkflow(row); } }, { label: t('workflows.action.edit'), icon: (_row: Workflow) => { return ; }, onClick: (row: Workflow) => { handleEditWorkflow(row); } }, { label: t('workflows.action.delete'), icon: (_row: Workflow) => { return ; }, onClick: (row: Workflow) => { if (!deletingWorkflows.has(row.id)) { handleDeleteWorkflow(row); } } }, ], [t, deletingWorkflows, handleDeleteWorkflow, handleEditWorkflow, handlePlayWorkflow]); return { // Data workflows, loading, error, workflowMessageCounts, editModalOpen, editingWorkflow, editWorkflowFields, // Actions handleDeleteSingle, handleDeleteMultiple, handleEditWorkflow, handleSaveWorkflow, handleCancelEdit, handlePlayWorkflow, // Refetch function refetch, // Additional data for rendering columns: columns as any, actions: actions as any }; }