import React, { useState } from 'react'; import { Box, Typography, Button, IconButton, Divider, Chip, Grid, Card, CardContent, Collapse, Dialog, DialogTitle, DialogContent, DialogActions, TextField, FormControlLabel, Switch, Alert, Stack, Tooltip, Paper, } from '@mui/material'; import { ContentCopy as CopyIcon, ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon, Refresh as RefreshIcon, Block as BlockIcon, Schedule as ScheduleIcon, Speed as SpeedIcon, Folder as FolderIcon, CloudOff as CloudOffIcon, Timer as TimerIcon, Info as InfoIcon, Warning as WarningIcon, } from '@mui/icons-material'; import { alpha } from '@mui/material/styles'; import { WebDAVScanFailure } from '../../services/api'; import { modernTokens } from '../../theme'; import { useNotification } from '../../contexts/NotificationContext'; interface FailureDetailsPanelProps { failure: WebDAVScanFailure; onRetry: (failure: WebDAVScanFailure, notes?: string) => Promise; onExclude: (failure: WebDAVScanFailure, notes?: string, permanent?: boolean) => Promise; isRetrying?: boolean; isExcluding?: boolean; } interface ConfirmationDialogProps { open: boolean; onClose: () => void; onConfirm: (notes?: string, permanent?: boolean) => void; title: string; description: string; confirmText: string; confirmColor?: 'primary' | 'error' | 'warning'; showPermanentOption?: boolean; isLoading?: boolean; } const ConfirmationDialog: React.FC = ({ open, onClose, onConfirm, title, description, confirmText, confirmColor = 'primary', showPermanentOption = false, isLoading = false, }) => { const [notes, setNotes] = useState(''); const [permanent, setPermanent] = useState(true); const handleConfirm = () => { onConfirm(notes || undefined, showPermanentOption ? permanent : undefined); setNotes(''); setPermanent(true); }; const handleClose = () => { setNotes(''); setPermanent(true); onClose(); }; return ( {title} {description} setNotes(e.target.value)} multiline rows={3} sx={{ mb: 2 }} /> {showPermanentOption && ( setPermanent(e.target.checked)} /> } label="Permanently exclude (recommended)" sx={{ mt: 1 }} /> )} ); }; const FailureDetailsPanel: React.FC = ({ failure, onRetry, onExclude, isRetrying = false, isExcluding = false, }) => { const [showDiagnostics, setShowDiagnostics] = useState(false); const [retryDialogOpen, setRetryDialogOpen] = useState(false); const [excludeDialogOpen, setExcludeDialogOpen] = useState(false); const { showNotification } = useNotification(); // Handle copy to clipboard const handleCopy = async (text: string, label: string) => { try { await navigator.clipboard.writeText(text); showNotification({ type: 'success', message: `${label} copied to clipboard`, }); } catch (error) { showNotification({ type: 'error', message: `Failed to copy ${label}`, }); } }; // Format bytes const formatBytes = (bytes?: number) => { if (!bytes) return 'N/A'; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`; }; // Format duration const formatDuration = (ms?: number) => { if (!ms) return 'N/A'; if (ms < 1000) return `${ms}ms`; const seconds = Math.floor(ms / 1000); if (seconds < 60) return `${seconds}s`; const minutes = Math.floor(seconds / 60); return `${minutes}m ${seconds % 60}s`; }; // Get recommendation color and icon const getRecommendationStyle = () => { if (failure.diagnostic_summary.user_action_required) { return { color: modernTokens.colors.warning[600], bgColor: modernTokens.colors.warning[50], icon: WarningIcon, }; } return { color: modernTokens.colors.info[600], bgColor: modernTokens.colors.info[50], icon: InfoIcon, }; }; const recommendationStyle = getRecommendationStyle(); const RecommendationIcon = recommendationStyle.icon; return ( {/* Error Message */} {failure.error_message && ( handleCopy(failure.error_message!, 'Error message')} > } > {failure.error_message} )} {/* Basic Information */} Directory Information Path {failure.directory_path} handleCopy(failure.directory_path, 'Directory path')} > Failure Count {failure.failure_count} total • {failure.consecutive_failures} consecutive Timeline First failure: {new Date(failure.first_failure_at).toLocaleString()} Last failure: {new Date(failure.last_failure_at).toLocaleString()} {failure.next_retry_at && ( Next retry: {new Date(failure.next_retry_at).toLocaleString()} )} {failure.http_status_code && ( HTTP Status )} Recommended Action {failure.diagnostic_summary.recommended_action} {failure.diagnostic_summary.can_retry && ( } label="Can retry" size="small" sx={{ backgroundColor: modernTokens.colors.success[100], color: modernTokens.colors.success[700], }} /> )} {failure.diagnostic_summary.user_action_required && ( } label="Action required" size="small" sx={{ backgroundColor: modernTokens.colors.warning[100], color: modernTokens.colors.warning[700], }} /> )} {/* Diagnostic Information (Collapsible) */} {failure.diagnostic_summary.path_length && ( {failure.diagnostic_summary.path_length} Path Length (chars) )} {failure.diagnostic_summary.directory_depth && ( {failure.diagnostic_summary.directory_depth} Directory Depth )} {failure.diagnostic_summary.estimated_item_count && ( {failure.diagnostic_summary.estimated_item_count.toLocaleString()} Estimated Items )} {failure.diagnostic_summary.response_time_ms && ( {formatDuration(failure.diagnostic_summary.response_time_ms)} Response Time )} {failure.diagnostic_summary.response_size_mb && ( {failure.diagnostic_summary.response_size_mb.toFixed(1)} MB Response Size )} {failure.diagnostic_summary.server_type && ( Server Type {failure.diagnostic_summary.server_type} )} {/* User Notes */} {failure.user_notes && ( User Notes: {failure.user_notes} )} {/* Action Buttons */} {!failure.resolved && !failure.user_excluded && ( {failure.diagnostic_summary.can_retry && ( )} )} {/* Confirmation Dialogs */} setRetryDialogOpen(false)} onConfirm={(notes) => { onRetry(failure, notes); setRetryDialogOpen(false); }} title="Retry WebDAV Scan" description={`This will attempt to scan "${failure.directory_path}" again. The failure will be reset and moved to the retry queue.`} confirmText="Retry Now" confirmColor="primary" isLoading={isRetrying} /> setExcludeDialogOpen(false)} onConfirm={(notes, permanent) => { onExclude(failure, notes, permanent); setExcludeDialogOpen(false); }} title="Exclude Directory from Scanning" description={`This will prevent "${failure.directory_path}" from being scanned in future synchronizations.`} confirmText="Exclude Directory" confirmColor="warning" showPermanentOption isLoading={isExcluding} /> ); }; export default FailureDetailsPanel;