227 lines
6.2 KiB
TypeScript
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;
|