Skip to content

Commit a545dcf

Browse files
committed
feat: Refactor ReportDetailPage with modular components for improved readability and maintainability
1 parent 2273b3d commit a545dcf

File tree

10 files changed

+431
-277
lines changed

10 files changed

+431
-277
lines changed

frontend/src/pages/Reports/ReportDetailPage.tsx

Lines changed: 28 additions & 277 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import { IonPage, IonContent } from '@ionic/react';
22
import { useState } from 'react';
33
import { useHistory, useParams } from 'react-router-dom';
4-
import Icon from '../../common/components/Icon/Icon';
54
import './ReportDetailPage.scss';
65
import { useQuery } from '@tanstack/react-query';
76
import axios from 'axios';
87
import { MedicalReport, LabValue } from '../../common/models/medicalReport';
98
import { useTranslation } from 'react-i18next';
109
import { getAuthConfig } from 'common/api/reportService';
11-
import { format } from 'date-fns';
10+
11+
// Import components
12+
import ReportHeader from './components/ReportHeader';
13+
import ReportTabs from './components/ReportTabs';
14+
import EmergencyAlert from './components/EmergencyAlert';
15+
import FlaggedValuesSection from './components/FlaggedValuesSection';
16+
import NormalValuesSection from './components/NormalValuesSection';
17+
import OriginalReport from './components/OriginalReport';
18+
import InfoCard from './components/InfoCard';
19+
import ActionButtons from './components/ActionButtons';
1220

1321
const API_URL = import.meta.env.VITE_BASE_URL_API || '';
1422

@@ -75,12 +83,11 @@ const ReportDetailPage: React.FC = () => {
7583

7684
const reportData = data;
7785

86+
// Process lab values data
7887
const hasEmergency = reportData.labValues.some((value) => value.isCritical);
79-
8088
const flaggedValues: LabValue[] = reportData.labValues.filter(
8189
(value) => value.status !== 'normal',
8290
);
83-
8491
const normalValues: LabValue[] = reportData.labValues.filter(
8592
(value) => value.status === 'normal',
8693
);
@@ -107,298 +114,42 @@ const ReportDetailPage: React.FC = () => {
107114
return (
108115
<IonPage className="report-detail-page">
109116
<IonContent fullscreen>
110-
{/* Header section */}
111-
<div className="report-detail-page__header">
112-
<div className="report-detail-page__title-container">
113-
<h1 className="report-detail-page__title">Results Analysis</h1>
114-
<button className="report-detail-page__close-button" onClick={handleClose}>
115-
<svg
116-
width="24"
117-
height="24"
118-
viewBox="0 0 24 24"
119-
fill="none"
120-
xmlns="http://www.w3.org/2000/svg"
121-
>
122-
<path
123-
d="M18 6L6 18"
124-
stroke="#435FF0"
125-
strokeWidth="2.5"
126-
strokeLinecap="round"
127-
strokeLinejoin="round"
128-
/>
129-
<path
130-
d="M6 6L18 18"
131-
stroke="#435FF0"
132-
strokeWidth="2.5"
133-
strokeLinecap="round"
134-
strokeLinejoin="round"
135-
/>
136-
</svg>
137-
</button>
138-
</div>
139-
140-
{/* Category & Title */}
141-
<div className="report-detail-page__category-wrapper">
142-
<span className="report-detail-page__category">
143-
{t(`list.${reportData.category}Category`, { ns: 'report' })}
144-
</span>
145-
<button className="report-detail-page__bookmark-button">
146-
<Icon icon="bookmark" iconStyle="regular" />
147-
</button>
148-
</div>
149-
<h2 className="report-detail-page__subtitle">{reportData.title}</h2>
150-
</div>
117+
{/* Header component */}
118+
<ReportHeader reportData={reportData} onClose={handleClose} />
151119

152120
{/* Tab selector for AI Insights vs Original Report */}
153-
<div className="report-detail-page__tabs">
154-
<div
155-
className={`report-detail-page__tab ${
156-
activeTab === 'ai' ? 'report-detail-page__tab--active' : ''
157-
}`}
158-
onClick={() => handleTabChange('ai')}
159-
>
160-
<svg
161-
className="report-detail-page__tab-icon-chevron"
162-
width="16"
163-
height="16"
164-
viewBox="0 0 24 24"
165-
fill="none"
166-
xmlns="http://www.w3.org/2000/svg"
167-
>
168-
<path
169-
d="M18.5 14.75L12 8.25L5.5 14.75"
170-
stroke="#435FF0"
171-
strokeWidth="2"
172-
strokeLinecap="round"
173-
strokeLinejoin="round"
174-
/>
175-
</svg>
176-
AI Insights
177-
</div>
178-
<div
179-
className={`report-detail-page__tab ${
180-
activeTab === 'original' ? 'report-detail-page__tab--active' : ''
181-
}`}
182-
onClick={() => handleTabChange('original')}
183-
>
184-
Original Report
185-
</div>
186-
</div>
121+
<ReportTabs activeTab={activeTab} onTabChange={handleTabChange} />
187122

188123
{/* Content based on active tab */}
189124
{activeTab === 'ai' ? (
190125
<>
191126
{/* Emergency alert if needed */}
192-
{hasEmergency && (
193-
<div className="report-detail-page__emergency">
194-
<div className="report-detail-page__emergency-icon">
195-
<svg
196-
width="20"
197-
height="20"
198-
viewBox="0 0 20 20"
199-
fill="none"
200-
xmlns="http://www.w3.org/2000/svg"
201-
>
202-
<path
203-
d="M9.25736 3.99072C9.52536 3.5167 10.1999 3.5167 10.4679 3.99072L17.8891 16.5347C18.1571 17.0087 17.8199 17.5999 17.2839 17.5999H2.44132C1.90536 17.5999 1.56816 17.0087 1.83616 16.5347L9.25736 3.99072Z"
204-
stroke="#C93A54"
205-
strokeWidth="1.5"
206-
strokeLinecap="round"
207-
strokeLinejoin="round"
208-
/>
209-
<path
210-
d="M9.8623 7.20001V11.2"
211-
stroke="#C93A54"
212-
strokeWidth="1.5"
213-
strokeLinecap="round"
214-
strokeLinejoin="round"
215-
/>
216-
<path
217-
d="M9.8623 14.4H9.87027"
218-
stroke="#C93A54"
219-
strokeWidth="1.5"
220-
strokeLinecap="round"
221-
strokeLinejoin="round"
222-
/>
223-
</svg>
224-
</div>
225-
<p className="report-detail-page__emergency-text">
226-
{t('report.emergency.message', { ns: 'reportDetail' })}
227-
</p>
228-
</div>
229-
)}
127+
{hasEmergency && <EmergencyAlert />}
230128

231129
{/* Flagged values section */}
232-
<div className="report-detail-page__section">
233-
<div className="report-detail-page__section-header" onClick={toggleFlaggedValues}>
234-
<div className="report-detail-page__section-icon">
235-
<Icon icon="flag" size="sm" style={{ color: '#c93a54' }} />
236-
</div>
237-
<h3 className="report-detail-page__section-title">
238-
{t('report.flagged-values.title', { ns: 'reportDetail' })}
239-
</h3>
240-
<div className="report-detail-page__section-toggle">
241-
<Icon icon={flaggedValuesExpanded ? 'chevronUp' : 'chevronDown'} size="sm" />
242-
</div>
243-
</div>
244-
245-
{flaggedValuesExpanded &&
246-
flaggedValues.map((item: LabValue, index) => (
247-
<div key={index} className="report-detail-page__item">
248-
<div
249-
className={`report-detail-page__item-header report-detail-page__item-header--${item.status.toLowerCase()}`}
250-
>
251-
<div className="report-detail-page__item-name">{item.name}</div>
252-
<div
253-
className={`report-detail-page__item-level report-detail-page__item-level--${item.status.toLowerCase()}`}
254-
>
255-
{item.status}
256-
</div>
257-
<div className="report-detail-page__item-value">
258-
{item.value} {item.unit}
259-
</div>
260-
</div>
261-
<div className="report-detail-page__item-details">
262-
<div className="report-detail-page__item-section">
263-
<h4>{t('report.conclusion.title', { ns: 'reportDetail' })}:</h4>
264-
<p>{item.conclusion}</p>
265-
</div>
266-
<div className="report-detail-page__item-section">
267-
<h4>{t('report.suggestions.title', { ns: 'reportDetail' })}:</h4>
268-
<p>{item.suggestions}</p>
269-
</div>
270-
</div>
271-
</div>
272-
))}
273-
</div>
130+
<FlaggedValuesSection
131+
flaggedValues={flaggedValues}
132+
isExpanded={flaggedValuesExpanded}
133+
onToggle={toggleFlaggedValues}
134+
/>
274135

275136
{/* Normal values section */}
276-
<div className="report-detail-page__section">
277-
<div className="report-detail-page__section-header" onClick={toggleNormalValues}>
278-
<div
279-
className="report-detail-page__section-icon"
280-
style={{ borderRadius: '50%', backgroundColor: '#f0f0f0' }}
281-
>
282-
<Icon icon="vial" size="sm" />
283-
</div>
284-
<h3 className="report-detail-page__section-title">
285-
{t('report.normal-values.title', { ns: 'reportDetail' })}
286-
</h3>
287-
<div className="report-detail-page__section-toggle">
288-
<Icon icon={normalValuesExpanded ? 'chevronUp' : 'chevronDown'} size="sm" />
289-
</div>
290-
</div>
291-
292-
{normalValuesExpanded &&
293-
normalValues.map((item: LabValue, index) => (
294-
<div key={index} className="report-detail-page__item">
295-
<div className="report-detail-page__item-header">
296-
<div className="report-detail-page__item-name">{item.name}</div>
297-
<div className="report-detail-page__item-value">
298-
{item.value} {item.unit}
299-
</div>
300-
</div>
301-
<div className="report-detail-page__item-details">
302-
<div className="report-detail-page__item-section">
303-
<h4>{t('report.conclusion.title', { ns: 'reportDetail' })}:</h4>
304-
<p>{item.conclusion}</p>
305-
</div>
306-
<div className="report-detail-page__item-section">
307-
<h4>{t('report.suggestions.title', { ns: 'reportDetail' })}:</h4>
308-
<p>{item.suggestions}</p>
309-
</div>
310-
</div>
311-
</div>
312-
))}
313-
</div>
137+
<NormalValuesSection
138+
normalValues={normalValues}
139+
isExpanded={normalValuesExpanded}
140+
onToggle={toggleNormalValues}
141+
/>
314142
</>
315143
) : (
316144
/* Original Report Tab Content */
317-
<div className="report-detail-page__original-report">
318-
{/* Test results table */}
319-
<div className="report-detail-page__results-table">
320-
<div className="report-detail-page__results-header">
321-
<div className="report-detail-page__results-cell report-detail-page__results-cell--test">
322-
Test
323-
</div>
324-
<div className="report-detail-page__results-cell report-detail-page__results-cell--value">
325-
Results
326-
</div>
327-
<div className="report-detail-page__results-cell report-detail-page__results-cell--ref">
328-
Ref.
329-
</div>
330-
</div>
331-
332-
{/* Test Results Rows */}
333-
{reportData.labValues.map((labValue, index) => (
334-
<div
335-
key={index}
336-
className={`report-detail-page__results-row ${
337-
labValue.status !== 'normal' ? 'report-detail-page__results-row--flagged' : ''
338-
}`}
339-
>
340-
<div className="report-detail-page__results-cell report-detail-page__results-cell--test">
341-
{labValue.name}
342-
</div>
343-
<div className="report-detail-page__results-cell report-detail-page__results-cell--value">
344-
{labValue.value} {labValue.unit}
345-
</div>
346-
<div className="report-detail-page__results-cell report-detail-page__results-cell--ref">
347-
{labValue.normalRange}
348-
</div>
349-
</div>
350-
))}
351-
</div>
352-
353-
{/* Uploaded File Section */}
354-
<div className="report-detail-page__uploaded-file">
355-
<h4 className="report-detail-page__uploaded-file-title">Uploaded file</h4>
356-
<div className="report-detail-page__file-container">
357-
<div className="report-detail-page__file-icon">
358-
<Icon icon="filePdf" />
359-
</div>
360-
<div className="report-detail-page__file-details">
361-
<div className="report-detail-page__file-name">
362-
{reportData.filePath.split('/').pop() || 'Exam_11_01_2024.pdf'}
363-
</div>
364-
<div className="report-detail-page__file-info">
365-
<span className="report-detail-page__file-size">92 kb</span>
366-
<span className="report-detail-page__file-separator"></span>
367-
<span className="report-detail-page__file-date">
368-
Uploaded ({format(new Date(reportData.createdAt), 'MM/dd/yyyy')})
369-
</span>
370-
</div>
371-
</div>
372-
</div>
373-
</div>
374-
</div>
145+
<OriginalReport reportData={reportData} />
375146
)}
376147

377148
{/* Doctor information note */}
378-
<div className="report-detail-page__info-card">
379-
<div className="report-detail-page__info-icon">
380-
<Icon icon="circleInfo" />
381-
</div>
382-
<div className="report-detail-page__info-text">
383-
{t('report.doctor-note', { ns: 'reportDetail' })}
384-
</div>
385-
</div>
149+
<InfoCard />
386150

387151
{/* Action buttons at the bottom */}
388-
<div className="report-detail-page__actions">
389-
<button
390-
className="report-detail-page__action-button report-detail-page__action-button--discard"
391-
onClick={handleDiscard}
392-
>
393-
{t('report.action.discard', { ns: 'reportDetail' })}
394-
</button>
395-
<button
396-
className="report-detail-page__action-button report-detail-page__action-button--upload"
397-
onClick={handleNewUpload}
398-
>
399-
{t('report.action.new-upload', { ns: 'reportDetail' })}
400-
</button>
401-
</div>
152+
<ActionButtons onDiscard={handleDiscard} onNewUpload={handleNewUpload} />
402153
</IonContent>
403154
</IonPage>
404155
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
4+
interface ActionButtonsProps {
5+
onDiscard: () => void;
6+
onNewUpload: () => void;
7+
}
8+
9+
const ActionButtons: React.FC<ActionButtonsProps> = ({ onDiscard, onNewUpload }) => {
10+
const { t } = useTranslation();
11+
12+
return (
13+
<div className="report-detail-page__actions">
14+
<button
15+
className="report-detail-page__action-button report-detail-page__action-button--discard"
16+
onClick={onDiscard}
17+
>
18+
{t('report.action.discard', { ns: 'reportDetail' })}
19+
</button>
20+
<button
21+
className="report-detail-page__action-button report-detail-page__action-button--upload"
22+
onClick={onNewUpload}
23+
>
24+
{t('report.action.new-upload', { ns: 'reportDetail' })}
25+
</button>
26+
</div>
27+
);
28+
};
29+
30+
export default ActionButtons;

0 commit comments

Comments
 (0)