Initial commit: HealthManager full-stack health management platform
Backend: .NET 10 + PostgreSQL + EF Core + JWT + SignalR Frontend patient: React 19 + TypeScript + Vite (mobile H5) Frontend doctor: React 19 + TypeScript + Vite (desktop web)
This commit is contained in:
114
frontend-doctor/src/pages/reports/ReportDetailPage.tsx
Normal file
114
frontend-doctor/src/pages/reports/ReportDetailPage.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { api } from '../../services/api-client';
|
||||
|
||||
interface RawReport {
|
||||
id: string; patientId: string; title: string; category: string;
|
||||
imageUrls: string[]; status: string; riskLevel?: string;
|
||||
summary?: string; suggestions?: string;
|
||||
patientName?: string; doctorName?: string;
|
||||
createdAt: string; completedAt?: string;
|
||||
items?: { id: string; itemName: string; resultValue: string; unit?: string; referenceRange?: string; isAbnormal: boolean }[];
|
||||
}
|
||||
|
||||
export function ReportDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [report, setReport] = useState<RawReport | null>(null);
|
||||
const [interpretation, setInterpretation] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
api.get<RawReport>(`/api/reports/${id}`).then((r) => setReport(r.data)).catch(() => {});
|
||||
}, [id]);
|
||||
|
||||
const handleInterpret = async () => {
|
||||
if (!interpretation.trim() || !id) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await api.post(`/api/reports/${id}/interpret`, {
|
||||
summary: interpretation,
|
||||
items: [],
|
||||
riskLevel: 'normal',
|
||||
suggestions: null,
|
||||
});
|
||||
// Refetch report to show updated status
|
||||
const updated = await api.get<RawReport>(`/api/reports/${id}`);
|
||||
setReport(updated.data);
|
||||
setInterpretation('');
|
||||
alert('interpretation submitted');
|
||||
} catch { alert('submit failed'); }
|
||||
finally { setSubmitting(false); }
|
||||
};
|
||||
|
||||
if (!report) return <div style={{ padding: 24 }}>loading...</div>;
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<Link to="/reports" style={{ fontSize: 13, color: '#1976d2' }}>← back to reports</Link>
|
||||
|
||||
<div style={{ background: '#fff', marginTop: 16, padding: 24, borderRadius: 8, boxShadow: '0 1px 4px rgba(0,0,0,0.08)' }}>
|
||||
<h2>{report.title}</h2>
|
||||
<div style={{ marginTop: 12, fontSize: 14, color: '#666' }}>
|
||||
<div>Patient: {report.patientName || 'unknown'}</div>
|
||||
<div>Category: {report.category}</div>
|
||||
<div>Status: {report.status}</div>
|
||||
<div>Submitted: {report.createdAt?.split('T')[0]}</div>
|
||||
</div>
|
||||
|
||||
{report.imageUrls && report.imageUrls.length > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<h4 style={{ fontSize: 14, marginBottom: 8 }}>Report Images</h4>
|
||||
{report.imageUrls.map((url, i) => (
|
||||
<div key={i} style={{ padding: '8px 12px', background: '#f5f5f5', borderRadius: 4, marginBottom: 4, fontSize: 12 }}>
|
||||
{url}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{report.status === 'completed' && (
|
||||
<div style={{ marginTop: 16, padding: 16, background: '#e8f5e9', borderRadius: 8 }}>
|
||||
<h4 style={{ fontSize: 14, marginBottom: 4 }}>Interpretation Result</h4>
|
||||
<p style={{ fontSize: 13, color: '#555' }}>{report.summary || 'No summary'}</p>
|
||||
{report.riskLevel && <p style={{ fontSize: 12, color: '#888' }}>Risk: {report.riskLevel}</p>}
|
||||
{report.suggestions && <p style={{ fontSize: 12, color: '#888' }}>Suggestions: {report.suggestions}</p>}
|
||||
{report.items && report.items.length > 0 && (
|
||||
<table style={{ width: '100%', marginTop: 8, borderCollapse: 'collapse', fontSize: 12 }}>
|
||||
<thead><tr style={{ textAlign: 'left', borderBottom: '1px solid #ddd' }}>
|
||||
<th style={{ padding: 4 }}>Item</th><th style={{ padding: 4 }}>Value</th><th style={{ padding: 4 }}>Range</th><th style={{ padding: 4 }}>Abnormal</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{report.items.map((item) => (
|
||||
<tr key={item.id} style={{ borderBottom: '1px solid #f0f0f0' }}>
|
||||
<td style={{ padding: 4 }}>{item.itemName}</td>
|
||||
<td style={{ padding: 4 }}>{item.resultValue} {item.unit || ''}</td>
|
||||
<td style={{ padding: 4 }}>{item.referenceRange || '-'}</td>
|
||||
<td style={{ padding: 4, color: item.isAbnormal ? '#c62828' : '#2e7d32' }}>{item.isAbnormal ? 'Yes' : 'No'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{report.status !== 'completed' && (
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<h4 style={{ fontSize: 14, marginBottom: 8 }}>Doctor Interpretation</h4>
|
||||
<textarea value={interpretation} onChange={(e) => setInterpretation(e.target.value)}
|
||||
placeholder="Enter your interpretation..."
|
||||
rows={5}
|
||||
style={{ width: '100%', padding: 12, border: '1px solid #ddd', borderRadius: 4, fontSize: 14, resize: 'vertical' }} />
|
||||
<button onClick={handleInterpret} disabled={submitting} style={{
|
||||
marginTop: 8, padding: '10px 24px', background: '#1976d2', color: '#fff',
|
||||
border: 'none', borderRadius: 4, fontSize: 14, opacity: submitting ? 0.7 : 1,
|
||||
}}>
|
||||
{submitting ? 'Submitting...' : 'Submit Interpretation'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user