296 lines
10 KiB
TypeScript
296 lines
10 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Button,
|
|
Typography,
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableContainer,
|
|
TableHead,
|
|
TableRow,
|
|
Paper,
|
|
Alert,
|
|
LinearProgress,
|
|
Box,
|
|
Chip,
|
|
Tooltip,
|
|
IconButton,
|
|
} from '@mui/material';
|
|
import {
|
|
History as HistoryIcon,
|
|
Close as CloseIcon,
|
|
Refresh as RefreshIcon,
|
|
Schedule as ScheduleIcon,
|
|
PriorityHigh as PriorityIcon,
|
|
} from '@mui/icons-material';
|
|
import { documentService, DocumentRetryHistoryItem } from '../services/api';
|
|
import { format, formatDistanceToNow } from 'date-fns';
|
|
|
|
interface RetryHistoryModalProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
documentId: string;
|
|
documentName?: string;
|
|
}
|
|
|
|
const RETRY_REASON_LABELS: Record<string, string> = {
|
|
manual_retry: 'Manual Retry',
|
|
bulk_retry_all: 'Bulk Retry (All)',
|
|
bulk_retry_specific: 'Bulk Retry (Selected)',
|
|
bulk_retry_filtered: 'Bulk Retry (Filtered)',
|
|
scheduled_retry: 'Scheduled Retry',
|
|
auto_retry: 'Automatic Retry',
|
|
};
|
|
|
|
const STATUS_COLORS: Record<string, 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning'> = {
|
|
pending: 'info',
|
|
processing: 'warning',
|
|
completed: 'success',
|
|
failed: 'error',
|
|
cancelled: 'default',
|
|
};
|
|
|
|
export const RetryHistoryModal: React.FC<RetryHistoryModalProps> = ({
|
|
open,
|
|
onClose,
|
|
documentId,
|
|
documentName,
|
|
}) => {
|
|
const [history, setHistory] = useState<DocumentRetryHistoryItem[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [totalRetries, setTotalRetries] = useState(0);
|
|
|
|
const loadRetryHistory = async () => {
|
|
if (!documentId) return;
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const response = await documentService.getDocumentRetryHistory(documentId);
|
|
setHistory(response.data?.retry_history || []);
|
|
setTotalRetries(response.data?.total_retries || 0);
|
|
} catch (err: any) {
|
|
setError(err.response?.data?.message || 'Failed to load retry history');
|
|
setHistory([]);
|
|
setTotalRetries(0);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (open && documentId) {
|
|
loadRetryHistory();
|
|
}
|
|
}, [open, documentId]);
|
|
|
|
const formatRetryReason = (reason: string) => {
|
|
return RETRY_REASON_LABELS[reason] || reason.replace(/_/g, ' ');
|
|
};
|
|
|
|
const getPriorityLabel = (priority: number) => {
|
|
if (priority >= 15) return 'Very High';
|
|
if (priority >= 12) return 'High';
|
|
if (priority >= 8) return 'Medium';
|
|
if (priority >= 5) return 'Low';
|
|
return 'Very Low';
|
|
};
|
|
|
|
const getPriorityColor = (priority: number): 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' => {
|
|
if (priority >= 15) return 'error';
|
|
if (priority >= 12) return 'warning';
|
|
if (priority >= 8) return 'primary';
|
|
if (priority >= 5) return 'info';
|
|
return 'default';
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onClose={onClose} maxWidth="lg" fullWidth>
|
|
<DialogTitle>
|
|
<Box display="flex" alignItems="center" justifyContent="space-between">
|
|
<Box display="flex" alignItems="center" gap={1}>
|
|
<HistoryIcon />
|
|
<Box>
|
|
<Typography variant="h6">OCR Retry History</Typography>
|
|
{documentName && (
|
|
<Typography variant="body2" color="text.secondary">
|
|
{documentName}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
<IconButton onClick={onClose} size="small">
|
|
<CloseIcon />
|
|
</IconButton>
|
|
</Box>
|
|
</DialogTitle>
|
|
|
|
<DialogContent>
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 2 }}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
{loading ? (
|
|
<Box>
|
|
<LinearProgress />
|
|
<Typography variant="body2" color="text.secondary" mt={1} textAlign="center">
|
|
Loading retry history...
|
|
</Typography>
|
|
</Box>
|
|
) : (!history || history.length === 0) ? (
|
|
<Alert severity="info">
|
|
<Typography variant="body1">
|
|
No retry attempts found for this document.
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary" mt={1}>
|
|
This document hasn't been retried yet, or retry history is not available.
|
|
</Typography>
|
|
</Alert>
|
|
) : (
|
|
<Box>
|
|
{/* Summary */}
|
|
<Alert severity="info" sx={{ mb: 3 }}>
|
|
<Typography variant="body1">
|
|
<strong>{totalRetries}</strong> retry attempts found for this document.
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Most recent attempt: {history && history.length > 0 ? formatDistanceToNow(new Date(history[0].created_at)) + ' ago' : 'No attempts yet'}
|
|
</Typography>
|
|
</Alert>
|
|
|
|
{/* History Table */}
|
|
<TableContainer component={Paper}>
|
|
<Table>
|
|
<TableHead>
|
|
<TableRow>
|
|
<TableCell>Date & Time</TableCell>
|
|
<TableCell>Retry Reason</TableCell>
|
|
<TableCell>Previous Status</TableCell>
|
|
<TableCell>Priority</TableCell>
|
|
<TableCell>Queue Status</TableCell>
|
|
</TableRow>
|
|
</TableHead>
|
|
<TableBody>
|
|
{(history || []).map((item, index) => (
|
|
<TableRow key={item.id} hover>
|
|
<TableCell>
|
|
<Box>
|
|
<Typography variant="body2">
|
|
{format(new Date(item.created_at), 'MMM dd, yyyy')}
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
{format(new Date(item.created_at), 'h:mm a')}
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
({formatDistanceToNow(new Date(item.created_at))} ago)
|
|
</Typography>
|
|
</Box>
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
<Chip
|
|
label={formatRetryReason(item.retry_reason)}
|
|
size="small"
|
|
variant="outlined"
|
|
/>
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
<Box>
|
|
{item.previous_status && (
|
|
<Chip
|
|
label={item.previous_status}
|
|
size="small"
|
|
color={STATUS_COLORS[item.previous_status] || 'default'}
|
|
sx={{ mb: 0.5 }}
|
|
/>
|
|
)}
|
|
{item.previous_failure_reason && (
|
|
<Typography variant="caption" display="block" color="text.secondary">
|
|
{item.previous_failure_reason.replace(/_/g, ' ')}
|
|
</Typography>
|
|
)}
|
|
{item.previous_error && (
|
|
<Tooltip title={item.previous_error}>
|
|
<Typography variant="caption" display="block" color="error.main" sx={{
|
|
maxWidth: 200,
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
whiteSpace: 'nowrap',
|
|
cursor: 'help'
|
|
}}>
|
|
{item.previous_error}
|
|
</Typography>
|
|
</Tooltip>
|
|
)}
|
|
</Box>
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
<Tooltip title={`Priority: ${item.priority}/20`}>
|
|
<Chip
|
|
icon={<PriorityIcon fontSize="small" />}
|
|
label={`${getPriorityLabel(item.priority)} (${item.priority})`}
|
|
size="small"
|
|
color={getPriorityColor(item.priority)}
|
|
/>
|
|
</Tooltip>
|
|
</TableCell>
|
|
|
|
<TableCell>
|
|
{item.queue_id ? (
|
|
<Box>
|
|
<Typography variant="body2" color="success.main">
|
|
✓ Queued
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
ID: {item.queue_id.slice(0, 8)}...
|
|
</Typography>
|
|
</Box>
|
|
) : (
|
|
<Typography variant="body2" color="warning.main">
|
|
⚠ Not queued
|
|
</Typography>
|
|
)}
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
|
|
{/* Legend */}
|
|
<Box mt={2} p={2} bgcolor="grey.50" borderRadius={1}>
|
|
<Typography variant="caption" color="text.secondary" paragraph>
|
|
<strong>Priority Levels:</strong> Very High (15-20), High (12-14), Medium (8-11), Low (5-7), Very Low (1-4)
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
<strong>Retry Reasons:</strong> Manual (user-initiated), Bulk (batch operations), Scheduled (automatic), Auto (system-triggered)
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
</DialogContent>
|
|
|
|
<DialogActions>
|
|
<Button
|
|
startIcon={<RefreshIcon />}
|
|
onClick={loadRetryHistory}
|
|
disabled={loading}
|
|
>
|
|
Refresh
|
|
</Button>
|
|
<Button onClick={onClose} variant="contained">
|
|
Close
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
}; |