Skip to content

Commit 5e5c48f

Browse files
committed
Enhance LabValueItem component to intelligently parse suggestions and improve status label handling
1 parent f23471d commit 5e5c48f

File tree

3 files changed

+150
-48
lines changed

3 files changed

+150
-48
lines changed

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

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
fetchLatestReports,
66
fetchAllReports,
77
markReportAsRead,
8+
getAuthConfig,
89
} from '../reportService';
910
import { ReportCategory, ReportStatus } from '../../models/medicalReport';
1011
import axios from 'axios';
@@ -22,6 +23,17 @@ vi.mock('axios', () => ({
2223
},
2324
}));
2425

26+
// Mock auth
27+
vi.mock('@aws-amplify/auth', () => ({
28+
fetchAuthSession: vi.fn().mockResolvedValue({
29+
tokens: {
30+
idToken: {
31+
toString: () => 'mock-id-token',
32+
},
33+
},
34+
}),
35+
}));
36+
2537
// Mock dynamic imports to handle the service functions
2638
vi.mock('../reportService', async (importOriginal) => {
2739
const actual = (await importOriginal()) as typeof ReportServiceModule;
@@ -31,6 +43,15 @@ vi.mock('../reportService', async (importOriginal) => {
3143
// Keep the ReportError class
3244
ReportError: actual.ReportError,
3345

46+
// Mock getAuthConfig to avoid authentication issues in tests
47+
getAuthConfig: async () => ({
48+
headers: {
49+
Accept: 'application/json',
50+
'Content-Type': 'application/json',
51+
Authorization: 'Bearer mock-id-token',
52+
},
53+
}),
54+
3455
// Mock the API functions
3556
uploadReport: async (file: File, onProgress?: (progress: number) => void) => {
3657
try {
@@ -80,23 +101,24 @@ vi.mock('../reportService', async (importOriginal) => {
80101
}
81102
},
82103

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

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-
100122
// Mock response data
101123
const mockReports = [
102124
{

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

frontend/src/pages/Reports/components/LabValueItem.tsx

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,69 @@ interface LabValueItemProps {
99
const LabValueItem: React.FC<LabValueItemProps> = ({ item }) => {
1010
const { t } = useTranslation();
1111

12+
// Parse suggestions into bullet points more intelligently
13+
const getSuggestionsList = (suggestions: string): string[] => {
14+
if (!suggestions) return [];
15+
16+
// Handle case where the text is already separated by bullet points
17+
if (suggestions.includes('•')) {
18+
return suggestions
19+
.split('•')
20+
.filter(Boolean)
21+
.map((item) => item.trim());
22+
}
23+
24+
// Handle case where items are separated by hyphens
25+
if (suggestions.includes('-')) {
26+
return suggestions
27+
.split('-')
28+
.filter(Boolean)
29+
.map((item) => item.trim());
30+
}
31+
32+
// Handle case with numbered lists (1., 2., etc.)
33+
if (/\d+\.\s/.test(suggestions)) {
34+
return suggestions
35+
.split(/\d+\.\s/)
36+
.filter(Boolean)
37+
.map((item) => item.trim());
38+
}
39+
40+
// Split by periods if it seems like sentences
41+
if (suggestions.includes('.')) {
42+
// Don't split on decimal points in numbers (e.g. "10.5")
43+
const sentences = suggestions
44+
.replace(/(\d+)\.(\d+)/g, '$1@$2')
45+
.split('.')
46+
.map((s) => s.replace(/@/g, '.').trim())
47+
.filter(Boolean);
48+
return sentences;
49+
}
50+
51+
// If we can't detect a pattern, return the whole string as one item
52+
return [suggestions];
53+
};
54+
55+
const suggestionItems = getSuggestionsList(item.suggestions);
56+
57+
// Determine classes and text for status label based on status
58+
const getStatusInfo = () => {
59+
if (item.status === 'high') {
60+
return {
61+
className: 'report-detail-page__item-level--high',
62+
text: t('report.high', { ns: 'reportDetail', defaultValue: 'High' }),
63+
};
64+
} else if (item.status === 'low') {
65+
return {
66+
className: 'report-detail-page__item-level--low',
67+
text: t('report.low', { ns: 'reportDetail', defaultValue: 'Low' }),
68+
};
69+
}
70+
return { className: '', text: '' };
71+
};
72+
73+
const statusInfo = getStatusInfo();
74+
1275
return (
1376
<div className="report-detail-page__item">
1477
<div
@@ -20,10 +83,8 @@ const LabValueItem: React.FC<LabValueItemProps> = ({ item }) => {
2083
>
2184
<div className="report-detail-page__item-name">{item.name}</div>
2285
{item.status !== 'normal' && (
23-
<div
24-
className={`report-detail-page__item-level report-detail-page__item-level--${item.status.toLowerCase()}`}
25-
>
26-
{item.status}
86+
<div className={`report-detail-page__item-level ${statusInfo.className}`}>
87+
{statusInfo.text}
2788
</div>
2889
)}
2990
<div className="report-detail-page__item-value">
@@ -32,12 +93,24 @@ const LabValueItem: React.FC<LabValueItemProps> = ({ item }) => {
3293
</div>
3394
<div className="report-detail-page__item-details">
3495
<div className="report-detail-page__item-section">
35-
<h4>{t('report.conclusion.title', { ns: 'reportDetail' })}:</h4>
96+
<h4>
97+
{t('report.conclusion.title', { ns: 'reportDetail', defaultValue: 'Conclusion' })}:
98+
</h4>
3699
<p>{item.conclusion}</p>
37100
</div>
38101
<div className="report-detail-page__item-section">
39-
<h4>{t('report.suggestions.title', { ns: 'reportDetail' })}:</h4>
40-
<p>{item.suggestions}</p>
102+
<h4>
103+
{t('report.suggestions.title', { ns: 'reportDetail', defaultValue: 'Suggestions' })}:
104+
</h4>
105+
{suggestionItems.length > 0 ? (
106+
<ul className="report-detail-page__item-list">
107+
{suggestionItems.map((suggestion, index) => (
108+
<li key={index}>{suggestion}</li>
109+
))}
110+
</ul>
111+
) : (
112+
<p>{item.suggestions}</p>
113+
)}
41114
</div>
42115
</div>
43116
</div>

0 commit comments

Comments
 (0)