diff --git a/frontend/src/pages/Processing/ProcessingPage.scss b/frontend/src/pages/Processing/ProcessingPage.scss index a6a4c546..6fad6df2 100644 --- a/frontend/src/pages/Processing/ProcessingPage.scss +++ b/frontend/src/pages/Processing/ProcessingPage.scss @@ -83,6 +83,103 @@ } } } + + &__error-container { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 2rem; + max-width: 500px; + margin: 0 auto; + } + + &__error-header { + margin-bottom: 1.5rem; + } + + &__error-oops { + font-size: 1.25rem; + color: var(--ion-color-medium); + margin-bottom: 0.5rem; + } + + &__error-title { + font-size: 2rem; + font-weight: 600; + margin: 0; + color: var(--ion-color-dark); + } + + &__error-icon { + margin: 1.5rem 0; + } + + &__error-icon-circle { + width: 120px; + height: 120px; + background-color: rgba(196, 30, 58, 0.1); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + } + + &__error-subheading { + font-size: 1.5rem; + font-weight: 600; + color: var(--ion-color-dark); + margin-bottom: 1rem; + } + + &__error-message { + font-size: 1rem; + color: var(--ion-color-medium); + line-height: 1.5; + margin-bottom: 2rem; + max-width: 400px; + } + + &__error-actions { + display: flex; + gap: 1rem; + width: 100%; + max-width: 400px; + } + + &__retry-btn { + flex: 1; + padding: 0.75rem 1rem; + border-radius: 2rem; + background: transparent; + border: 1px solid var(--ion-color-primary); + color: var(--ion-color-primary); + font-weight: 500; + font-size: 1rem; + cursor: pointer; + transition: background-color 0.2s, color 0.2s; + + &:hover { + background-color: var(--ion-color-primary-tint); + } + } + + &__upload-btn { + flex: 1; + padding: 0.75rem 1rem; + border-radius: 2rem; + background: var(--ion-color-primary); + border: none; + color: white; + font-weight: 500; + font-size: 1rem; + cursor: pointer; + transition: background-color 0.2s; + + &:hover { + background-color: var(--ion-color-primary-shade); + } + } } // Animation for the pulse effect diff --git a/frontend/src/pages/Processing/ProcessingPage.tsx b/frontend/src/pages/Processing/ProcessingPage.tsx index fa02b8d5..d4a8a2dc 100644 --- a/frontend/src/pages/Processing/ProcessingPage.tsx +++ b/frontend/src/pages/Processing/ProcessingPage.tsx @@ -6,6 +6,9 @@ import { useEffect, useState, useRef } from 'react'; import { useAxios } from '../../common/hooks/useAxios'; import './ProcessingPage.scss'; import { getAuthConfig } from 'common/api/reportService'; +import ProcessingError from './components/ProcessingError'; +import ProcessingAnimation from './components/ProcessingAnimation'; + const API_URL = import.meta.env.VITE_BASE_URL_API || ''; /** @@ -28,6 +31,13 @@ const ProcessingPage: React.FC = () => { const location = useLocation<{ reportId: string }>(); const reportId = location.state?.reportId; + const clearStatusCheckInterval = () => { + if (statusCheckIntervalRef.current) { + window.clearInterval(statusCheckIntervalRef.current); + statusCheckIntervalRef.current = null; + } + }; + // Check the status of the report processing const checkReportStatus = async () => { if (!reportId) return; @@ -38,36 +48,64 @@ const ProcessingPage: React.FC = () => { await getAuthConfig(), ); - console.log('Report status:', response.data); + const data = response.data; // If processing is complete, clear the interval and redirect to the report page - if (response.data.isComplete) { + if (data.isComplete) { setIsProcessing(false); // Clear the interval - if (statusCheckIntervalRef.current) { - window.clearInterval(statusCheckIntervalRef.current); - statusCheckIntervalRef.current = null; - } + clearStatusCheckInterval(); console.log('Processing complete'); // Redirect to report detail page history.push(`/tabs/reports/${reportId}`); + } else if (data.status === 'failed') { + throw new Error('Processing failed'); } } catch (error) { + // Clear the interval on error + clearStatusCheckInterval(); + console.error('Error checking report status:', error); - setProcessingError('Failed to check the status of the report. Please try again.'); + setProcessingError('An error occurred while processing the report. Please try again.'); setIsProcessing(false); + } + }; - // Clear the interval on error - if (statusCheckIntervalRef.current) { - window.clearInterval(statusCheckIntervalRef.current); - statusCheckIntervalRef.current = null; - } + // Process file function - moved outside useEffect to make it callable from buttons + const processFile = async () => { + if (!reportId) { + return; + } + + try { + // Send POST request to backend API + await axios.post( + `${API_URL}/api/document-processor/process-file`, + { reportId }, + await getAuthConfig(), + ); + + // Start checking the status every 2 seconds + statusCheckIntervalRef.current = window.setInterval(checkReportStatus, 2000); + } catch (error) { + console.error('Error processing file:', error); + setProcessingError('Failed to process the file. Please try again.'); + setIsProcessing(false); } }; + // Handle retry attempt + const handleRetry = () => { + // Reset error state and try processing the same file again + setProcessingError(null); + setIsProcessing(true); + hasInitiatedProcessing.current = false; + processFile(); + }; + // Send the API request when component mounts useEffect(() => { // Use ref to ensure this effect runs only once for the core logic @@ -75,49 +113,14 @@ const ProcessingPage: React.FC = () => { return; } - if (!reportId) { - setProcessingError('No report ID provided'); - setIsProcessing(false); - hasInitiatedProcessing.current = true; // Mark as initiated even on error - return; - } - hasInitiatedProcessing.current = true; // Mark as initiated before fetching - const processFile = async () => { - try { - // Send POST request to backend API - const response = await axios.post( - `${API_URL}/api/document-processor/process-file`, - { reportId }, - await getAuthConfig(), - ); - - console.log('File processing started:', response.data); - - // Start checking the status every 2 seconds - statusCheckIntervalRef.current = window.setInterval(checkReportStatus, 2000); - - // Run the first status check immediately - checkReportStatus(); - } catch (error) { - console.error('Error processing file:', error); - setProcessingError('Failed to process the file. Please try again.'); - setIsProcessing(false); - } - }; - processFile(); // Clean up the interval when the component unmounts - return () => { - if (statusCheckIntervalRef.current) { - window.clearInterval(statusCheckIntervalRef.current); - } - }; + return clearStatusCheckInterval; }, [reportId, location, history]); - return ( @@ -132,36 +135,14 @@ const ProcessingPage: React.FC = () => { testid="processing-user-avatar" /> - - {/* Title section */} -
-

- Just a few seconds{firstName && ', ' + firstName}! -

-

- {processingError ? 'Processing Error' : 'Processing Data...'} -

- {processingError &&

{processingError}

} -
- {/* Animation circle */} - {isProcessing && ( -
-
-
- )} + {/* Processing animation */} + {isProcessing && } - {/* Error state - show retry button */} + {/* Error state - shows when processing fails */} {processingError && ( -
- -
+ )}
@@ -169,4 +150,4 @@ const ProcessingPage: React.FC = () => { ); }; -export default ProcessingPage; \ No newline at end of file +export default ProcessingPage; diff --git a/frontend/src/pages/Processing/components/ProcessingAnimation.tsx b/frontend/src/pages/Processing/components/ProcessingAnimation.tsx new file mode 100644 index 00000000..1f6e73e2 --- /dev/null +++ b/frontend/src/pages/Processing/components/ProcessingAnimation.tsx @@ -0,0 +1,26 @@ +import '../ProcessingPage.scss'; + +interface ProcessingAnimationProps { + firstName?: string; +} + +/** + * Component that displays processing animation and message + */ +const ProcessingAnimation: React.FC = ({ firstName }) => { + return ( + <> +
+

+ Just a few seconds{firstName && ', ' + firstName}! +

+

Processing Data...

+
+
+
+
+ + ); +}; + +export default ProcessingAnimation; diff --git a/frontend/src/pages/Processing/components/ProcessingError.tsx b/frontend/src/pages/Processing/components/ProcessingError.tsx new file mode 100644 index 00000000..e85ccf81 --- /dev/null +++ b/frontend/src/pages/Processing/components/ProcessingError.tsx @@ -0,0 +1,62 @@ +import { useHistory } from 'react-router-dom'; +import '../ProcessingPage.scss'; + +interface ProcessingErrorProps { + errorMessage: string; + onRetry: () => void; +} + +/** + * Component that displays processing error information and actions + */ +const ProcessingError: React.FC = ({ errorMessage, onRetry }) => { + const history = useHistory(); + + return ( +
+
+

Oops! ...

+

Problem detected

+
+ +
+
+ + + +
+
+ +

Processing Error

+ +

+ {errorMessage || + 'There was a problem processing your uploaded file. Please try again or upload another.'} +

+ +
+ + + +
+
+ ); +}; + +export default ProcessingError;