Skip to content

Commit b1108f7

Browse files
committed
Create AI Insights page
1 parent ab25c8a commit b1108f7

File tree

3 files changed

+246
-29
lines changed

3 files changed

+246
-29
lines changed

frontend/src/common/utils/i18n/resources/en/report.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,20 @@
2727
"insight2Content": "Both your LDL cholesterol and total cholesterol are elevated, which may increase your risk for cardiovascular disease.",
2828
"insight3Title": "Blood Glucose",
2929
"insight3Content": "Your fasting blood glucose is elevated, potentially indicating prediabetes. Lifestyle modifications may help improve this value.",
30-
"hemoglobinComment": "The patient's hemoglobin level is 12.5 g/dL, which falls within the lower end of the normal reference range for most adults. While this value may still be considered acceptable, it is important to assess it in the context of the patient's age, sex, clinical symptoms, and medical history."
30+
"hemoglobinComment": "The patient's hemoglobin level is 12.5 g/dL, which falls within the lower end of the normal reference range for most adults. While this value may still be considered acceptable, it is important to assess it in the context of the patient's age, sex, clinical symptoms, and medical history.",
31+
"emergencyWarning": "Please contact your doctor or seek emergency care immediately.",
32+
"flaggedValues": "Flagged values",
33+
"highLdl": "High LDL Cholesterol",
34+
"lowHemoglobin": "Low Hemoglobin (10.1 g/dL)",
35+
"conclusion": "Conclusion:",
36+
"suggestions": "Suggestions:",
37+
"ldlConclusion": "Elevated LDL (bad cholesterol) increases your risk of cardiovascular disease",
38+
"ldlSuggestion1": "Consider a heart-healthy diet (e.g., Mediterranean).",
39+
"ldlSuggestion2": "Increase physical activity.",
40+
"ldlSuggestion3": "Discuss statin therapy with your doctor if not already on one.",
41+
"hemoglobinConclusion": "This level suggests anemia, which may cause fatigue and weakness.",
42+
"hemoglobinSuggestion1": "Test for iron, B12, and folate deficiency.",
43+
"hemoglobinSuggestion2": "Consider iron-rich foods or supplements after medical consultation already on one."
3144
},
3245
"list": {
3346
"title": "Medical Reports",

frontend/src/pages/Reports/ReportDetailPage.scss

Lines changed: 141 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
--background: var(--ion-color-light);
33

44
&__header {
5+
background-color: #fff;
56
border-bottom: 1px solid var(--ion-color-light-shade);
67
padding-bottom: 1rem;
78

@@ -55,7 +56,7 @@
5556

5657
ion-segment-button {
5758
--color: var(--ion-color-medium);
58-
--color-checked: var(--ion-color-dark);
59+
--color-checked: var(--ion-color-black);
5960
--background-checked: var(--ion-color-primary-contrast);
6061
--indicator-color: transparent;
6162
}
@@ -148,25 +149,153 @@
148149

149150
// AI Insights Tab Styling
150151
.ai-insights {
151-
p {
152-
line-height: 1.5;
152+
.warning-alert {
153+
display: flex;
154+
align-items: center;
155+
background-color: #fff2f2;
156+
border: 1px solid #ffdbdb;
157+
color: var(--ion-color-danger);
158+
border-radius: 8px;
159+
padding: 0.75rem 1rem;
160+
margin-bottom: 1.5rem;
161+
162+
ion-icon {
163+
font-size: 1.5rem;
164+
margin-right: 0.75rem;
165+
min-width: 24px;
166+
}
167+
168+
p {
169+
margin: 0;
170+
font-weight: 500;
171+
line-height: 1.4;
172+
}
173+
}
174+
175+
.flagged-values-section {
176+
background-color: #fff;
177+
border-radius: 8px;
178+
border: 1px solid var(--ion-color-light-shade);
153179
margin-bottom: 1rem;
180+
overflow: hidden;
154181
}
155182

156-
.insight-list {
157-
padding-left: 1.5rem;
158-
margin: 0;
183+
.flagged-values-header {
184+
display: flex;
185+
justify-content: space-between;
186+
align-items: center;
187+
padding: 0.8rem 1rem;
188+
cursor: pointer;
189+
background-color: var(--ion-color-light);
159190

160-
li {
191+
.flagged-values-title {
192+
display: flex;
193+
align-items: center;
194+
195+
ion-icon {
196+
font-size: 1.2rem;
197+
margin-right: 0.5rem;
198+
color: var(--ion-color-medium);
199+
}
200+
201+
h3 {
202+
margin: 0;
203+
font-size: 1rem;
204+
font-weight: 600;
205+
color: var(--ion-color-dark);
206+
}
207+
}
208+
209+
ion-icon {
210+
font-size: 1.2rem;
211+
color: var(--ion-color-medium);
212+
}
213+
}
214+
215+
.flagged-values-content {
216+
padding: 0;
217+
}
218+
219+
.flagged-value-item {
220+
padding: 1rem;
221+
border-bottom: 1px solid var(--ion-color-light-shade);
222+
223+
&:last-child {
224+
border-bottom: none;
225+
}
226+
}
227+
228+
.flagged-value-title {
229+
display: flex;
230+
align-items: center;
231+
margin-bottom: 0.75rem;
232+
font-weight: 600;
233+
234+
span {
235+
margin-right: 0.5rem;
236+
}
237+
238+
.value-with-units {
239+
margin-left: auto;
240+
font-weight: 600;
241+
}
242+
}
243+
244+
.severity-badge {
245+
padding: 0.2rem 0.5rem;
246+
border-radius: 4px;
247+
font-size: 0.8rem;
248+
font-weight: 600;
249+
text-transform: uppercase;
250+
251+
&.severity-high {
252+
background-color: #ffdbdb;
253+
color: var(--ion-color-danger);
254+
}
255+
256+
&.severity-low {
257+
background-color: #fff1db;
258+
color: #ff9500;
259+
}
260+
261+
&.severity-normal {
262+
background-color: #dbffef;
263+
color: #34c759;
264+
}
265+
}
266+
267+
.flagged-value-details {
268+
.conclusion, .suggestions {
161269
margin-bottom: 1rem;
162-
line-height: 1.4;
163270

164-
&:last-child {
165-
margin-bottom: 0;
271+
h4 {
272+
font-size: 0.9rem;
273+
font-weight: 600;
274+
margin: 0 0 0.25rem 0;
275+
color: var(--ion-color-medium);
276+
}
277+
278+
p {
279+
margin: 0;
280+
font-size: 0.9rem;
281+
line-height: 1.4;
282+
color: var(--ion-color-dark);
166283
}
284+
}
285+
286+
ul {
287+
margin: 0;
288+
padding-left: 1.2rem;
289+
290+
li {
291+
font-size: 0.9rem;
292+
line-height: 1.4;
293+
margin-bottom: 0.5rem;
294+
color: var(--ion-color-dark);
167295

168-
strong {
169-
color: var(--ion-color-primary);
296+
&:last-child {
297+
margin-bottom: 0;
298+
}
170299
}
171300
}
172301
}

frontend/src/pages/Reports/ReportDetailPage.tsx

Lines changed: 91 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import {
1010
IonText,
1111
IonButton,
1212
IonIcon,
13-
IonToast
13+
IonToast,
1414
} from '@ionic/react';
1515
import { useState, useEffect } from 'react';
1616
import { useParams } from 'react-router-dom';
1717
import { useTranslation } from 'react-i18next';
18-
import { bookmark, bookmarkOutline } from 'ionicons/icons';
18+
import { bookmark, bookmarkOutline, warningOutline, chevronDown, chevronUp } from 'ionicons/icons';
1919
import { MedicalReport } from 'common/models/medicalReport';
2020
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
2121
import { fetchAllReports, toggleReportBookmark } from 'common/api/reportService';
@@ -38,6 +38,16 @@ interface BloodTestData {
3838
comments?: string;
3939
}
4040

41+
interface FlaggedValue {
42+
id: string;
43+
title: string;
44+
value: string;
45+
units: string;
46+
severity: 'High' | 'Low' | 'Normal';
47+
conclusion: string;
48+
suggestions: string[];
49+
}
50+
4151
/**
4252
* Page component for displaying detailed information about a medical report.
4353
* Shows both AI insights and actual test results.
@@ -49,6 +59,7 @@ const ReportDetailPage: React.FC = () => {
4959
const [report, setReport] = useState<MedicalReport | null>(null);
5060
const [showBookmarkToast, setShowBookmarkToast] = useState(false);
5161
const [bookmarkToastMessage, setBookmarkToastMessage] = useState('');
62+
const [flaggedValuesExpanded, setFlaggedValuesExpanded] = useState(true);
5263
const queryClient = useQueryClient();
5364

5465
// Fetch all reports and find the one we need
@@ -107,6 +118,35 @@ const ReportDetailPage: React.FC = () => {
107118
comments: t('detail.hemoglobinComment')
108119
};
109120

121+
// Mock flagged values data for AI insights
122+
const flaggedValues: FlaggedValue[] = [
123+
{
124+
id: 'ldl',
125+
title: t('detail.highLdl'),
126+
value: '165',
127+
units: 'mg/dL',
128+
severity: 'High',
129+
conclusion: t('detail.ldlConclusion'),
130+
suggestions: [
131+
t('detail.ldlSuggestion1'),
132+
t('detail.ldlSuggestion2'),
133+
t('detail.ldlSuggestion3')
134+
]
135+
},
136+
{
137+
id: 'hemoglobin',
138+
title: t('detail.lowHemoglobin'),
139+
value: '10.1',
140+
units: 'g/dL',
141+
severity: 'Low',
142+
conclusion: t('detail.hemoglobinConclusion'),
143+
suggestions: [
144+
t('detail.hemoglobinSuggestion1'),
145+
t('detail.hemoglobinSuggestion2')
146+
]
147+
}
148+
];
149+
110150
// Find the report from the list
111151
useEffect(() => {
112152
if (reports.length > 0 && reportId) {
@@ -124,6 +164,11 @@ const ReportDetailPage: React.FC = () => {
124164
setSelectedSegment(e.detail.value);
125165
};
126166

167+
// Toggle flagged values section
168+
const toggleFlaggedValues = () => {
169+
setFlaggedValuesExpanded(!flaggedValuesExpanded);
170+
};
171+
127172
if (!report) {
128173
return (
129174
<IonPage className="report-detail-page">
@@ -180,20 +225,50 @@ const ReportDetailPage: React.FC = () => {
180225
<IonCardContent>
181226
{selectedSegment === 'aiInsights' && (
182227
<div className="ai-insights">
183-
<p>
184-
{t('detail.aiInsightsContent')}
185-
</p>
186-
<ul className="insight-list">
187-
<li>
188-
<strong>{t('detail.insight1Title')}</strong>: {t('detail.insight1Content')}
189-
</li>
190-
<li>
191-
<strong>{t('detail.insight2Title')}</strong>: {t('detail.insight2Content')}
192-
</li>
193-
<li>
194-
<strong>{t('detail.insight3Title')}</strong>: {t('detail.insight3Content')}
195-
</li>
196-
</ul>
228+
<div className="warning-alert">
229+
<IonIcon icon={warningOutline} />
230+
<p>{t('detail.emergencyWarning')}</p>
231+
</div>
232+
233+
<div className="flagged-values-section">
234+
<div className="flagged-values-header" onClick={toggleFlaggedValues}>
235+
<div className="flagged-values-title">
236+
<IonIcon icon="flag-outline" />
237+
<h3>{t('detail.flaggedValues')}</h3>
238+
</div>
239+
<IonIcon icon={flaggedValuesExpanded ? chevronUp : chevronDown} />
240+
</div>
241+
242+
{flaggedValuesExpanded && (
243+
<div className="flagged-values-content">
244+
{flaggedValues.map((value) => (
245+
<div key={value.id} className="flagged-value-item">
246+
<div className="flagged-value-title">
247+
<span>{value.title}</span>
248+
<div className={`severity-badge severity-${value.severity.toLowerCase()}`}>
249+
{value.severity}
250+
</div>
251+
<span className="value-with-units">{value.value} {value.units}</span>
252+
</div>
253+
<div className="flagged-value-details">
254+
<div className="conclusion">
255+
<h4>{t('detail.conclusion')}</h4>
256+
<p>{value.conclusion}</p>
257+
</div>
258+
<div className="suggestions">
259+
<h4>{t('detail.suggestions')}</h4>
260+
<ul>
261+
{value.suggestions.map((suggestion, index) => (
262+
<li key={index}>{suggestion}</li>
263+
))}
264+
</ul>
265+
</div>
266+
</div>
267+
</div>
268+
))}
269+
</div>
270+
)}
271+
</div>
197272
</div>
198273
)}
199274

0 commit comments

Comments
 (0)