Skip to content

Commit aca7eab

Browse files
authored
Merge pull request #108 from ModusCreateOrg/ADE-66
[ADE-66] fixes and improvements to uploading, processing, and viewing a report
2 parents 66e62c7 + 4914e45 commit aca7eab

File tree

11 files changed

+216
-79
lines changed

11 files changed

+216
-79
lines changed

backend/src/reports/reports.controller.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ export class ReportsController {
9292
throw new NotFoundException('Processing failed');
9393
}
9494

95+
if (report.processingStatus === ProcessingStatus.IN_PROGRESS) {
96+
throw new NotFoundException('Processing in progress');
97+
}
98+
99+
if (report.processingStatus === ProcessingStatus.UNPROCESSED) {
100+
throw new NotFoundException('Processing pending');
101+
}
102+
95103
return report;
96104
}
97105

backend/src/reports/reports.service.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,23 +55,23 @@ export class ReportsService {
5555
this.tableName = this.configService.get<string>('dynamodbReportsTable')!;
5656
}
5757

58-
async findAll(userId: string, withFailed = false): Promise<Report[]> {
58+
async findAll(userId: string, onlyProcessed = true): Promise<Report[]> {
5959
if (!userId) {
6060
throw new ForbiddenException('User ID is required');
6161
}
6262

6363
try {
6464
const expressionAttributeValues: any = { ':userId': userId };
65-
const processingStatusFilter = 'processingStatus <> :failedStatus';
65+
const processingStatusFilter = 'processingStatus = :processedStatus';
6666

67-
if (!withFailed) {
68-
expressionAttributeValues[':failedStatus'] = ProcessingStatus.FAILED;
67+
if (onlyProcessed) {
68+
expressionAttributeValues[':processedStatus'] = ProcessingStatus.PROCESSED;
6969
}
7070

7171
const command = new QueryCommand({
7272
TableName: this.tableName,
7373
KeyConditionExpression: 'userId = :userId',
74-
FilterExpression: !withFailed ? processingStatusFilter : undefined,
74+
FilterExpression: onlyProcessed ? processingStatusFilter : undefined,
7575
ExpressionAttributeValues: marshall(expressionAttributeValues),
7676
});
7777

@@ -100,7 +100,7 @@ export class ReportsService {
100100
async findLatest(
101101
queryDto: GetReportsQueryDto,
102102
userId: string,
103-
withFailed = false,
103+
onlyProcessed = true,
104104
): Promise<Report[]> {
105105
this.logger.log(
106106
`Running findLatest with params: ${JSON.stringify(queryDto)} for user ${userId}`,
@@ -116,17 +116,17 @@ export class ReportsService {
116116
const expressionAttributeValues: any = { ':userId': userId };
117117

118118
try {
119-
const processingStatusFilter = 'processingStatus <> :failedStatus';
119+
const processingStatusFilter = 'processingStatus = :processedStatus';
120120

121-
if (!withFailed) {
122-
expressionAttributeValues[':failedStatus'] = ProcessingStatus.FAILED;
121+
if (onlyProcessed) {
122+
expressionAttributeValues[':processedStatus'] = ProcessingStatus.PROCESSED;
123123
}
124124

125125
const command = new QueryCommand({
126126
TableName: this.tableName,
127127
IndexName: 'userIdCreatedAtIndex',
128128
KeyConditionExpression: 'userId = :userId',
129-
FilterExpression: !withFailed ? processingStatusFilter : undefined,
129+
FilterExpression: onlyProcessed ? processingStatusFilter : undefined,
130130
ExpressionAttributeValues: marshall(expressionAttributeValues),
131131
ScanIndexForward: false,
132132
Limit: limit,

frontend/src/common/api/__tests__/reportService.test.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ vi.mock('axios', () => ({
2222
},
2323
}));
2424

25+
// Mock auth
26+
vi.mock('@aws-amplify/auth', () => ({
27+
fetchAuthSession: vi.fn().mockResolvedValue({
28+
tokens: {
29+
idToken: {
30+
toString: () => 'mock-id-token',
31+
},
32+
},
33+
}),
34+
}));
35+
2536
// Mock dynamic imports to handle the service functions
2637
vi.mock('../reportService', async (importOriginal) => {
2738
const actual = (await importOriginal()) as typeof ReportServiceModule;
@@ -31,6 +42,15 @@ vi.mock('../reportService', async (importOriginal) => {
3142
// Keep the ReportError class
3243
ReportError: actual.ReportError,
3344

45+
// Mock getAuthConfig to avoid authentication issues in tests
46+
getAuthConfig: async () => ({
47+
headers: {
48+
Accept: 'application/json',
49+
'Content-Type': 'application/json',
50+
Authorization: 'Bearer mock-id-token',
51+
},
52+
}),
53+
3454
// Mock the API functions
3555
uploadReport: async (file: File, onProgress?: (progress: number) => void) => {
3656
try {
@@ -80,23 +100,24 @@ vi.mock('../reportService', async (importOriginal) => {
80100
}
81101
},
82102

83-
// Keep other functions as is
84-
markReportAsRead: actual.markReportAsRead,
85-
getAuthConfig: actual.getAuthConfig,
103+
// Mock markReportAsRead to avoid the dependency on getAuthConfig
104+
markReportAsRead: async (reportId: string) => {
105+
try {
106+
const response = await axios.patch(`/api/reports/${reportId}`, {
107+
status: 'READ',
108+
});
109+
return response.data;
110+
} catch (error) {
111+
throw new actual.ReportError(
112+
error instanceof Error
113+
? `Failed to mark report as read: ${error.message || 'Unknown error'}`
114+
: 'Failed to mark report as read',
115+
);
116+
}
117+
},
86118
};
87119
});
88120

89-
// Mock auth
90-
vi.mock('@aws-amplify/auth', () => ({
91-
fetchAuthSession: vi.fn().mockResolvedValue({
92-
tokens: {
93-
idToken: {
94-
toString: () => 'mock-id-token',
95-
},
96-
},
97-
}),
98-
}));
99-
100121
// Mock response data
101122
const mockReports = [
102123
{

frontend/src/common/api/reportService.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,18 @@ export const fetchAllReports = async (): Promise<MedicalReport[]> => {
141141
*/
142142
export const markReportAsRead = async (reportId: string): Promise<MedicalReport> => {
143143
try {
144-
const response = await axios.patch(`${API_URL}/api/reports/${reportId}`, {
145-
status: 'READ',
146-
});
144+
const response = await axios.patch(
145+
`${API_URL}/api/reports/${reportId}`,
146+
{
147+
status: 'READ',
148+
},
149+
await getAuthConfig(),
150+
);
147151

148152
return response.data;
149153
} catch (error) {
150154
if (axios.isAxiosError(error)) {
151-
throw new ReportError(`Failed to mark report as read: ${error.message}`);
155+
throw new ReportError(`Failed to mark report as read: ${error.message || 'Unknown error'}`);
152156
}
153157
throw new ReportError('Failed to mark report as read');
154158
}
@@ -176,7 +180,9 @@ export const toggleReportBookmark = async (
176180
return response.data;
177181
} catch (error) {
178182
if (axios.isAxiosError(error)) {
179-
throw new ReportError(`Failed to toggle bookmark status: ${error.message}`);
183+
throw new ReportError(
184+
`Failed to toggle bookmark status: ${error.message || 'Unknown error'}`,
185+
);
180186
}
181187
throw new ReportError('Failed to toggle bookmark status');
182188
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
5959
// Automatically redirect to processing screen after 2 seconds
6060
setTimeout(() => {
6161
reset();
62-
onClose();
62+
6363
if (onUploadComplete) {
6464
onUploadComplete(result);
6565
}
66+
6667
// Navigate to the processing tab with reportId in state
6768
if (file) {
6869
history.push('/tabs/processing', {
@@ -115,6 +116,7 @@ const UploadModal = ({ isOpen, onClose, onUploadComplete }: UploadModalProps): J
115116
// call onUploadComplete now before closing the modal
116117
if (status === UploadStatus.SUCCESS && uploadResult && onUploadComplete) {
117118
onUploadComplete(uploadResult);
119+
return;
118120
}
119121

120122
// Reset state

frontend/src/common/hooks/useReports.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ export const useGetLatestReports = (limit = 3) => {
1515
return useQuery({
1616
queryKey: [LATEST_REPORTS_KEY, limit],
1717
queryFn: () => fetchLatestReports(limit),
18+
refetchOnMount: true,
19+
refetchOnWindowFocus: true,
20+
staleTime: 0, // Consider data immediately stale so it always refreshes
1821
});
1922
};
2023

frontend/src/pages/Processing/ProcessingPage.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,21 @@ const ProcessingPage: React.FC = () => {
8282

8383
try {
8484
// Send POST request to backend API
85-
await axios.post(
85+
const response = await axios.post(
8686
`${API_URL}/api/document-processor/process-file`,
8787
{ reportId },
8888
await getAuthConfig(),
8989
);
9090

91+
const data = response.data;
92+
93+
if (data.status === 'processed') {
94+
setIsProcessing(false);
95+
96+
// Redirect to report detail page
97+
history.push(`/tabs/reports/${reportId}`);
98+
}
99+
91100
// Start checking the status every 2 seconds
92101
statusCheckIntervalRef.current = window.setInterval(checkReportStatus, 2000);
93102
} catch (error) {

frontend/src/pages/Reports/ReportDetailPage.scss

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -239,103 +239,110 @@
239239

240240
&__item {
241241
background-color: #ffffff;
242-
border-radius: 8px;
242+
border-radius: 16px;
243243
padding: 0;
244-
margin-top: 12px;
245-
margin-bottom: 8px;
246-
border-top: 1px solid #ebeef8;
244+
margin: 16px;
247245
overflow: hidden;
246+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
247+
border: 1px solid #ebeef8;
248248
}
249249

250250
&__item-header {
251251
display: flex;
252252
flex-wrap: wrap;
253253
align-items: center;
254-
padding: 12px 16px;
254+
padding: 16px;
255255
background-color: #fff;
256256

257257
&--high {
258258
background-color: rgba(201, 58, 84, 0.08);
259+
border-bottom: 1px solid rgba(201, 58, 84, 0.15);
259260
}
260261

261262
&--low {
262-
background-color: rgba(108, 99, 255, 0.08);
263+
background-color: rgba(250, 173, 113, 0.08);
264+
border-bottom: 1px solid rgba(250, 173, 113, 0.15);
263265
}
264266
}
265267

266268
&__item-name {
267-
font-weight: 500;
268-
font-size: 15px;
269+
font-weight: 600;
270+
font-size: 16px;
269271
margin-right: 12px;
270272
flex: 1;
271273
letter-spacing: 0.26px;
272-
line-height: 15.7px;
274+
line-height: 20px;
273275
color: #313e4c;
274276
}
275277

276278
&__item-level {
277-
font-size: 12px;
278-
padding: 2px 6px;
279-
border-radius: 4px;
279+
font-size: 13px;
280+
padding: 4px 12px;
281+
border-radius: 12px;
280282
margin-right: 16px;
281283
font-weight: 600;
282-
text-transform: uppercase;
284+
text-transform: capitalize;
283285

284286
&--high {
285-
background-color: rgba(201, 58, 84, 0.1);
286-
color: #c93a54;
287+
background-color: #c93a54;
288+
color: white;
287289
}
288290

289291
&--low {
290-
background-color: rgba(108, 99, 255, 0.1);
291-
color: #435ff0;
292+
background-color: #ffcf99;
293+
color: white;
292294
}
293295
}
294296

295297
&__item-value {
296298
font-weight: 600;
297-
font-size: 15px;
299+
font-size: 16px;
298300
white-space: nowrap;
299301
color: #313e4c;
300302
}
301303

302304
&__item-details {
303305
color: #444;
304-
padding: 10px 16px 16px;
305-
background-color: rgba(235, 238, 248, 0.3);
306+
padding: 16px;
307+
background-color: rgba(248, 249, 251, 0.6);
306308
}
307309

308310
&__item-section {
309-
margin-bottom: 12px;
311+
margin-bottom: 20px;
310312

311313
&:last-child {
312314
margin-bottom: 0;
313315
}
314316

315317
h4 {
316-
font-size: 14px;
318+
font-size: 15px;
317319
color: #313e4c;
318-
margin: 0 0 4px 0;
320+
margin: 0 0 8px 0;
319321
font-weight: 600;
320322
}
321323

322324
p {
323325
margin: 0;
324326
font-size: 14px;
325-
line-height: 1.4;
327+
line-height: 1.5;
326328
color: #5c6d80;
327329
}
328330
}
329331

330332
&__item-list {
331333
margin: 0;
332-
padding-left: 18px;
333-
font-size: 13px;
334+
padding-left: 20px;
335+
font-size: 14px;
334336
line-height: 1.5;
337+
color: #5c6d80;
335338

336339
li {
337-
margin-bottom: 6px;
340+
margin-bottom: 10px;
338341
padding-left: 4px;
342+
343+
&:last-child {
344+
margin-bottom: 0;
345+
}
339346
}
340347
}
341348

0 commit comments

Comments
 (0)