frontend_nyla/src/components/UiComponents/WorkflowStatus/WorkflowStatus.tsx
2026-03-16 14:26:37 +01:00

95 lines
3.4 KiB
TypeScript

import React, { useMemo } from 'react';
import { WorkflowStatusProps, WorkflowStatusType } from './WorkflowStatusTypes';
import styles from './WorkflowStatus.module.css';
const _STATUS_MAP: Record<string, WorkflowStatusType> = {
success: 'completed',
completed: 'completed',
started: 'started',
running: 'started',
resumed: 'resumed',
stopped: 'stopped',
failed: 'failed',
error: 'failed',
};
const extractWorkflowStatus = (logs: any[]): { status: WorkflowStatusType; round: number | null; timestamp: number } => {
if (!logs.length) return { status: null, round: null, timestamp: 0 };
const sorted = [...logs].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
for (const log of sorted) {
const logStatus = (log.status || '').toLowerCase();
const mapped = _STATUS_MAP[logStatus];
if (mapped) {
const roundMatch = (log.message || '').match(/\(?round\s+(\d+)\)?/i);
return { status: mapped, round: roundMatch ? parseInt(roundMatch[1], 10) : null, timestamp: log.timestamp || 0 };
}
}
return { status: null, round: null, timestamp: 0 };
};
const _formatCurrency = (amount?: number): string => {
if (amount === undefined || amount === null) return '-';
return `${amount.toFixed(2)} CHF`;
};
const WorkflowStatus: React.FC<WorkflowStatusProps> = ({
className = '',
logs = [],
workflowStatus: workflowStatusFromApi,
currentRound: currentRoundFromApi,
isRunning,
latestStats
}) => {
// Use workflow status and round from API response, fallback to extracting from logs
const workflowStatus = useMemo(() => {
if (workflowStatusFromApi) {
const mapped = _STATUS_MAP[workflowStatusFromApi.toLowerCase()] || null;
return { status: mapped, round: currentRoundFromApi || null, timestamp: Date.now() / 1000 };
}
return extractWorkflowStatus(logs);
}, [workflowStatusFromApi, currentRoundFromApi, logs]);
// Determine if workflow is running (show spinner)
// Show spinner if explicitly running OR if status indicates running state
const showSpinner = isRunning === true || workflowStatus.status === 'started' || workflowStatus.status === 'resumed';
// Don't render if no status information and no stats (but always show if spinner should be visible)
if (!showSpinner && !workflowStatus.status && workflowStatus.round === null && !latestStats) {
return null;
}
return (
<div className={`${styles.workflowStatusContainer} ${className}`}>
{/* Status and Round Badges */}
<div className={styles.workflowStatus}>
{showSpinner && (
<div className={styles.spinner} aria-label="Workflow running" />
)}
{workflowStatus.status && (
<span className={styles.statusBadge} data-status={workflowStatus.status}>
{workflowStatus.status.charAt(0).toUpperCase() + workflowStatus.status.slice(1)}
</span>
)}
{workflowStatus.round !== null && (
<span className={styles.roundBadge}>Round {workflowStatus.round}</span>
)}
</div>
{/* Cost Display */}
{latestStats && latestStats.priceCHF !== undefined && (
<div className={styles.statsContainer}>
<div className={styles.statItem}>
<span className={styles.statLabel}>Cost:</span>
<span className={styles.statValue}>{_formatCurrency(latestStats.priceCHF)}</span>
</div>
</div>
)}
</div>
);
};
export default WorkflowStatus;