frontend_nyla/src/components/Workflows/WorkflowsTable.tsx

227 lines
6.2 KiB
TypeScript

import { useMemo } from 'react';
import { FormGenerator, ColumnConfig } from '../FormGenerator/FormGenerator';
import { useWorkflows, useWorkflowOperations, Workflow } from '../../hooks/useWorkflows';
import { useLanguage } from '../../contexts/LanguageContext';
import styles from './WorkflowsTable.module.css';
interface WorkflowsTableProps {
className?: string;
}
function WorkflowsTable({ className = '' }: WorkflowsTableProps) {
const { workflows, loading, error, refetch } = useWorkflows();
const {
stopWorkflow,
deleteWorkflow,
stoppingWorkflows,
deletingWorkflows
} = useWorkflowOperations();
const { t } = useLanguage();
// Configure columns for the workflows table
const columns: ColumnConfig[] = 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) => (
<span className={styles.workflowId} title={value}>
{value.length > 8 ? `${value.substring(0, 8)}...` : value}
</span>
)
},
{
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, row: Workflow) => (
<span className={styles.workflowName}>
{value || row.title || t('workflows.unnamed')}
</span>
)
},
{
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) => (
<span className={`${styles.statusBadge} ${styles[`status-${value}`]}`}>
{t(`workflows.status.${value}`, value)}
</span>
)
},
{
key: 'currentRound',
label: t('workflows.column.round'),
type: 'number',
width: 80,
minWidth: 60,
maxWidth: 100,
sortable: true,
filterable: true,
formatter: (value: number | undefined) => (
<span className={styles.roundNumber}>
{value || 1}
</span>
)
},
{
key: 'startedAt',
label: t('workflows.column.started'),
type: 'date',
width: 140,
minWidth: 120,
maxWidth: 180,
sortable: true,
filterable: true,
formatter: (value: string | undefined) => {
if (!value) return '-';
try {
const date = new Date(value);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
} catch {
return value;
}
}
},
{
key: 'lastActivity',
label: t('workflows.column.lastActivity'),
type: 'date',
width: 140,
minWidth: 120,
maxWidth: 180,
sortable: true,
filterable: true,
formatter: (value: string | undefined) => {
if (!value) return '-';
try {
const date = new Date(value);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
} catch {
return value;
}
}
},
{
key: 'messages',
label: t('workflows.column.messages'),
type: 'number',
width: 100,
minWidth: 80,
maxWidth: 120,
sortable: true,
filterable: false,
formatter: (value: any[] | undefined) => (
<span className={styles.messageCount}>
{value?.length || 0}
</span>
)
}
], [t]);
// Handle workflow actions
const handleStopWorkflow = async (workflow: Workflow) => {
const success = await stopWorkflow(workflow.id);
if (success) {
refetch(); // Refresh the workflows list
}
};
const handleDeleteWorkflow = async (workflow: Workflow) => {
const workflowName = workflow.name || workflow.title || 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
}
}
};
// Configure action buttons
const actions = useMemo(() => [
{
label: t('workflows.action.stop'),
icon: (row: Workflow) => {
const isStoppingThis = stoppingWorkflows.has(row.id);
if (isStoppingThis) return '⏳';
return '⏹️';
},
onClick: (row: Workflow) => {
if (row.status === 'running' && !stoppingWorkflows.has(row.id)) {
handleStopWorkflow(row);
}
}
},
{
label: t('workflows.action.delete'),
icon: (row: Workflow) => {
const isDeletingThis = deletingWorkflows.has(row.id);
if (isDeletingThis) return '⏳';
return '🗑️';
},
onClick: (row: Workflow) => {
if (!deletingWorkflows.has(row.id)) {
handleDeleteWorkflow(row);
}
}
}
], [t, stoppingWorkflows, deletingWorkflows, handleStopWorkflow, handleDeleteWorkflow]);
// Show error state
if (error) {
return (
<div className={`${styles.workflowsTable} ${className}`}>
<div className={styles.errorState}>
<p>{t('workflows.error.loading')} {error}</p>
<button onClick={refetch} className={styles.retryButton}>
{t('workflows.button.retry')}
</button>
</div>
</div>
);
}
return (
<div className={`${styles.workflowsTable} ${className}`}>
<FormGenerator
data={workflows}
columns={columns}
title={t('workflows.table.title')}
searchable={true}
filterable={true}
sortable={true}
resizable={true}
pagination={true}
pageSize={10}
loading={loading}
actions={actions}
className={styles.workflowsFormGenerator}
onRowClick={(workflow: Workflow) => {
// TODO: Navigate to workflow detail view
console.log('Clicked workflow:', workflow);
}}
/>
</div>
);
}
export default WorkflowsTable;