Skip to content

Commit d6a1d35

Browse files
committed
feat: Implement file processing functionality and error handling in Processing page
1 parent 77879bc commit d6a1d35

File tree

4 files changed

+136
-56
lines changed

4 files changed

+136
-56
lines changed

backend/src/document-processor/controllers/document-processor.controller.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ export class DocumentProcessorController {
165165
return {
166166
success: true,
167167
reportId: report.id,
168-
analysis: result.analysis,
169168
};
170169
} catch (error: unknown) {
171170
this.logger.error(

backend/src/iac/backend-stack.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,13 @@ export class BackendStack extends cdk.Stack {
419419
},
420420
});
421421

422+
const processFileIntegration = new apigateway.Integration({
423+
type: apigateway.IntegrationType.HTTP_PROXY,
424+
integrationHttpMethod: 'POST',
425+
uri: `${serviceUrl}/api/document-processor/process-file`,
426+
options: integrationOptions,
427+
});
428+
422429
const patchReportStatusIntegration = new apigateway.Integration({
423430
type: apigateway.IntegrationType.HTTP_PROXY,
424431
integrationHttpMethod: 'PATCH',
@@ -457,6 +464,14 @@ export class BackendStack extends cdk.Stack {
457464
},
458465
});
459466

467+
// Add POST method to process file
468+
reportIdResource.addMethod('POST', processFileIntegration, {
469+
...methodOptions,
470+
requestParameters: {
471+
'method.request.path.id': true,
472+
},
473+
});
474+
460475
// Add CORS to each resource separately - after methods have been created
461476
const corsOptions = {
462477
allowOrigins: ['*'],

frontend/src/common/components/Upload/UploadModal.tsx

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ import {
66
IonIcon,
77
IonProgressBar,
88
IonLabel,
9-
IonItem
9+
IonItem,
1010
} from '@ionic/react';
11-
import { closeOutline, cloudUploadOutline, documentOutline, checkmarkOutline } from 'ionicons/icons';
11+
import {
12+
closeOutline,
13+
cloudUploadOutline,
14+
documentOutline,
15+
checkmarkOutline,
16+
} from 'ionicons/icons';
1217
import { useTranslation } from 'react-i18next';
1318
import { UploadStatus, useFileUpload } from '../../hooks/useFileUpload';
1419
import { MedicalReport } from '../../models/medicalReport';
@@ -34,7 +39,7 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
3439
const [uploadResult, setUploadResult] = useState<MedicalReport | null>(null);
3540
// Track when to show the upload cancelled notice
3641
const [showCancelNotice, setShowCancelNotice] = useState(false);
37-
42+
3843
const {
3944
file,
4045
status,
@@ -44,23 +49,30 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
4449
uploadFile,
4550
reset,
4651
formatFileSize,
47-
cancelUpload
48-
} = useFileUpload({
52+
cancelUpload,
53+
} = useFileUpload({
4954
// Override onUploadComplete to store the result and not call the parent immediately
50-
onUploadComplete: (result) => {
55+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
56+
onUploadComplete: (result: any) => {
5157
setUploadResult(result);
52-
58+
5359
// Automatically redirect to processing screen after 2 seconds
5460
setTimeout(() => {
5561
reset();
5662
onClose();
5763
if (onUploadComplete) {
5864
onUploadComplete(result);
5965
}
60-
// Navigate to the processing tab
61-
history.push('/tabs/processing');
66+
// Navigate to the processing tab with filePath in state
67+
if (file) {
68+
history.push('/tabs/processing', {
69+
filePath: result.filePath,
70+
});
71+
} else {
72+
history.push('/tabs/processing');
73+
}
6274
}, 2000);
63-
}
75+
},
6476
});
6577

6678
// Effect to automatically start upload when a file is selected and validated
@@ -77,7 +89,7 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
7789
if (!files || files.length === 0) {
7890
return;
7991
}
80-
92+
8193
selectFile(files[0]);
8294
// Upload will be triggered by the useEffect
8395
};
@@ -104,12 +116,12 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
104116
if (status === UploadStatus.SUCCESS && uploadResult && onUploadComplete) {
105117
onUploadComplete(uploadResult);
106118
}
107-
119+
108120
// Reset state
109121
reset();
110122
setUploadResult(null);
111123
setShowCancelNotice(false);
112-
124+
113125
// Close modal
114126
onClose();
115127
};
@@ -129,7 +141,7 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
129141
{t('upload.imageSizeLimit')} / {t('upload.pdfSizeLimit')}
130142
</p>
131143
</div>
132-
144+
133145
{/* Show cancel notice */}
134146
{showCancelNotice && (
135147
<div className="upload-modal__cancel-notice">
@@ -139,27 +151,23 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
139151
<span>Upload cancelled.</span>
140152
</div>
141153
)}
142-
143-
{error && !showCancelNotice &&
144-
<IonItem className='upload-modal__error-message'>
145-
<div className='upload-modal__error-icon' slot='start'>
146-
<FontAwesomeIcon icon={faCircleXmark} aria-hidden="true"/>
154+
155+
{error && !showCancelNotice && (
156+
<IonItem className="upload-modal__error-message">
157+
<div className="upload-modal__error-icon" slot="start">
158+
<FontAwesomeIcon icon={faCircleXmark} aria-hidden="true" />
147159
</div>
148160
<IonLabel className="upload-modal__error-label ion-text-wrap">{error}</IonLabel>
149161
</IonItem>
150-
}
162+
)}
151163
<input
152164
type="file"
153165
ref={fileInputRef}
154166
accept=".pdf,.jpeg,.jpg,.png"
155167
onChange={handleFileChange}
156168
className="upload-modal__file-input"
157169
/>
158-
<IonButton
159-
expand="block"
160-
className="upload-modal__upload-btn"
161-
onClick={handleUploadClick}
162-
>
170+
<IonButton expand="block" className="upload-modal__upload-btn" onClick={handleUploadClick}>
163171
{t('upload.selectFile')}
164172
</IonButton>
165173
</div>
@@ -170,7 +178,7 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
170178
<div className="upload-modal__initial">
171179
<div className="upload-modal__drop-area">
172180
<IonIcon icon={cloudUploadOutline} className="upload-modal__icon" />
173-
181+
174182
{/* File display item */}
175183
{file && (
176184
<div className="upload-modal__file-item">
@@ -183,19 +191,16 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
183191
{formatFileSize(file.size)}{Math.ceil((1 - progress) * 10)} seconds left
184192
</div>
185193
{/* Progress bar */}
186-
<IonProgressBar
187-
value={progress}
188-
className="upload-modal__progress"
189-
/>
194+
<IonProgressBar value={progress} className="upload-modal__progress" />
190195
</div>
191196
</div>
192197
)}
193198
</div>
194-
199+
195200
{/* Cancel button - updated to match the size of the upload button */}
196201
<div className="upload-modal__bottom-actions">
197-
<IonButton
198-
expand="block"
202+
<IonButton
203+
expand="block"
199204
fill="outline"
200205
className="upload-modal__cancel-btn"
201206
onClick={handleCancel}
@@ -234,11 +239,12 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
234239
};
235240

236241
// Show close button for success state but hide during uploading
237-
const showCloseButton = status !== UploadStatus.UPLOADING && status !== UploadStatus.REQUESTING_PERMISSION;
242+
const showCloseButton =
243+
status !== UploadStatus.UPLOADING && status !== UploadStatus.REQUESTING_PERMISSION;
238244

239245
return (
240-
<IonModal
241-
isOpen={isOpen}
246+
<IonModal
247+
isOpen={isOpen}
242248
onDidDismiss={handleClose}
243249
backdropDismiss={false}
244250
className="upload-modal"
@@ -247,11 +253,7 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
247253
<div className="upload-modal__header">
248254
<h1>{t('upload.addNewFile')}</h1>
249255
{showCloseButton && (
250-
<IonButton
251-
fill="clear"
252-
className="upload-modal__close-button"
253-
onClick={handleClose}
254-
>
256+
<IonButton fill="clear" className="upload-modal__close-button" onClick={handleClose}>
255257
<IonIcon icon={closeOutline} />
256258
</IonButton>
257259
)}
@@ -262,4 +264,4 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
262264
);
263265
};
264266

265-
export default UploadModal;
267+
export default UploadModal;
Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,64 @@
11
import { IonContent, IonPage } from '@ionic/react';
22
import { useCurrentUser } from '../../common/hooks/useAuth';
33
import Avatar from '../../common/components/Icon/Avatar';
4+
import { useLocation, useHistory } from 'react-router-dom';
5+
import { useEffect, useState } from 'react';
6+
import { useAxios } from '../../common/hooks/useAxios';
47
import './Processing.scss';
8+
import { getAuthConfig } from 'common/api/reportService';
9+
const API_URL = import.meta.env.VITE_BASE_URL_API || '';
510

611
/**
712
* Processing page that shows while the system analyzes uploaded documents
813
* This page automatically displays after a successful upload
914
*/
1015
const Processing: React.FC = () => {
1116
const currentUser = useCurrentUser();
12-
const firstName = currentUser?.firstName || currentUser?.name?.split(' ')[0] || 'Wesley';
17+
const firstName = currentUser?.name?.split(' ')[0];
18+
const axios = useAxios();
19+
const history = useHistory();
20+
21+
// States to track processing
22+
const [isProcessing, setIsProcessing] = useState(true);
23+
const [processingError, setProcessingError] = useState<string | null>(null);
24+
25+
// Get the location state which may contain the filePath
26+
const location = useLocation<{ filePath: string }>();
27+
const filePath = location.state?.filePath;
28+
const [reportId, setReportId] = useState(null);
29+
30+
// Send the API request when component mounts
31+
useEffect(() => {
32+
if (!filePath) {
33+
setProcessingError('No file path provided');
34+
setIsProcessing(false);
35+
return;
36+
}
37+
38+
if (reportId) {
39+
return;
40+
}
41+
42+
const processFile = async () => {
43+
try {
44+
// Send POST request to backend API
45+
const response = await axios.post(
46+
`${API_URL}/api/document-processor/process-file`,
47+
{ filePath },
48+
await getAuthConfig(),
49+
);
50+
setReportId(response.data.reportId);
51+
52+
console.log('File processed successfully:', response.data);
53+
} catch (error) {
54+
console.error('Error processing file:', error);
55+
setProcessingError('Failed to process the file. Please try again.');
56+
setIsProcessing(false);
57+
}
58+
};
59+
60+
processFile();
61+
}, [filePath, axios, history]);
1362

1463
return (
1564
<IonPage className="processing-page">
@@ -18,33 +67,48 @@ const Processing: React.FC = () => {
1867
{/* Header with avatar */}
1968
<div className="processing-page__header">
2069
<div className="processing-page__avatar-wrapper">
21-
<Avatar
22-
value={currentUser?.name || currentUser?.email || ''}
70+
<Avatar
71+
value={firstName || currentUser?.email || ''}
2372
size="large"
2473
shape="round"
2574
testid="processing-user-avatar"
2675
/>
2776
</div>
28-
77+
2978
{/* Title section */}
3079
<div className="processing-page__title">
3180
<p className="processing-page__subtitle">
32-
Just a few seconds, {firstName}!
81+
Just a few seconds{firstName && ', ' + firstName}!
3382
</p>
34-
<h1 className="processing-page__heading">Processing Data...</h1>
83+
<h1 className="processing-page__heading">
84+
{processingError ? 'Processing Error' : 'Processing Data...'}
85+
</h1>
86+
{processingError && <p className="processing-page__error">{processingError}</p>}
3587
</div>
3688
</div>
37-
89+
3890
{/* Animation circle */}
39-
<div className="processing-page__animation">
40-
<div className="processing-page__animation-circle"></div>
41-
</div>
42-
43-
{/* We don't need to include the tab bar here since it's global */}
91+
{isProcessing && (
92+
<div className="processing-page__animation">
93+
<div className="processing-page__animation-circle"></div>
94+
</div>
95+
)}
96+
97+
{/* Error state - show retry button */}
98+
{processingError && (
99+
<div className="processing-page__error-actions">
100+
<button
101+
className="processing-page__retry-btn"
102+
onClick={() => history.push('/tabs/upload')}
103+
>
104+
Try Again
105+
</button>
106+
</div>
107+
)}
44108
</div>
45109
</IonContent>
46110
</IonPage>
47111
);
48112
};
49113

50-
export default Processing;
114+
export default Processing;

0 commit comments

Comments
 (0)