fix: patient report shows interpretation, medication daily tracking, followup bugs, home overview restored, doctor renamed

This commit is contained in:
MingNian
2026-05-21 16:32:20 +08:00
parent 0df75c35e9
commit 4c85cd50be
7 changed files with 257 additions and 146 deletions

View File

@@ -6,7 +6,7 @@ const navItems = [
{ to: '/patients', label: '患者管理', icon: '👥' }, { to: '/patients', label: '患者管理', icon: '👥' },
{ to: '/consultations', label: '在线问诊', icon: '💬' }, { to: '/consultations', label: '在线问诊', icon: '💬' },
{ to: '/reports', label: '报告审核', icon: '📋' }, { to: '/reports', label: '报告审核', icon: '📋' },
{ to: '/follow-ups', label: '随访管理', icon: '📅' }, { to: '/follow-ups', label: '复查管理', icon: '📅' },
]; ];
const sidebarBg = '#0F1D3D'; const sidebarBg = '#0F1D3D';

View File

@@ -1,24 +1,20 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Card } from '@/components/common/Card'; import { Card } from '@/components/common/Card';
import { Empty } from '@/components/common/Empty';
import { Badge } from '@/components/common/Badge';
import { PageHeader } from '@/components/layout/PageHeader';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
import { useNotificationStore } from '@/stores/notification.store'; import { useNotificationStore } from '@/stores/notification.store';
import * as healthService from '@/services/health.service'; import * as healthService from '@/services/health.service';
import { MEASUREMENT_TYPES, HEALTH_TIPS } from '@/utils/constants';
import { getBPRiskLevel } from '@/utils/format'; import { getBPRiskLevel } from '@/utils/format';
import type { HealthStats } from '@/types'; import type { HealthStats } from '@/types';
import styles from './HomePage.module.css'; import styles from './HomePage.module.css';
const QUICK_ACTIONS = [ const QUICK_ACTIONS = [
{ label: '血压', icon: '💓', path: '/health/records?type=blood_pressure' }, { key: 'bp', label: '血压', icon: '💓', path: '/health/records?type=blood_pressure', badge: false },
{ label: '用药', icon: '💊', path: '/health/medications' }, { key: 'med', label: '用药', icon: '💊', path: '/health/medications', badge: false },
{ label: '在线问诊', icon: '💬', path: '/services/consultation' }, { key: 'chat', label: '问诊', icon: '💬', path: '/services/consultation', badge: true },
{ label: '报告解读', icon: '📋', path: '/services/reports' }, { key: 'report', label: '报告', icon: '📋', path: '/services/reports', badge: true },
{ label: '健康日历', icon: '📅', path: '/health/calendar' }, { key: 'calendar', label: '日历', icon: '📅', path: '/health/calendar', badge: false },
{ label: '运动饮食', icon: '🏃', path: '/health/exercise-diet' }, { key: 'followup', label: '复查', icon: '🏥', path: '/services/follow-ups', badge: false },
]; ];
export function HomePage() { export function HomePage() {
@@ -26,7 +22,6 @@ export function HomePage() {
const { user } = useAuth(); const { user } = useAuth();
const { unreadCount, fetchNotifications } = useNotificationStore(); const { unreadCount, fetchNotifications } = useNotificationStore();
const [stats, setStats] = useState<HealthStats[]>([]); const [stats, setStats] = useState<HealthStats[]>([]);
const [tipIndex, setTipIndex] = useState(0);
useEffect(() => { useEffect(() => {
healthService.getLatestStats().then(setStats); healthService.getLatestStats().then(setStats);
@@ -35,6 +30,8 @@ export function HomePage() {
const bpStats = stats.find((s) => s.type === 'blood_pressure'); const bpStats = stats.find((s) => s.type === 'blood_pressure');
const hrStats = stats.find((s) => s.type === 'heart_rate'); const hrStats = stats.find((s) => s.type === 'heart_rate');
const sugarStats = stats.find((s) => s.type === 'blood_sugar');
const spo2Stats = stats.find((s) => s.type === 'spo2');
const bpValue = bpStats?.latest?.value; const bpValue = bpStats?.latest?.value;
const systolic = typeof bpValue === 'object' ? bpValue.systolic : null; const systolic = typeof bpValue === 'object' ? bpValue.systolic : null;
@@ -43,76 +40,77 @@ export function HomePage() {
return ( return (
<div className="page"> <div className="page">
<PageHeader <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
title={`你好,${user?.nickname || '用户'}`} <div>
showBack={false} <div style={{ fontSize: 20, fontWeight: 700 }}>{user?.nickname || '用户'}</div>
rightAction={ <div style={{ fontSize: 12, color: '#9CA3AF', marginTop: 2 }}></div>
<button className={styles.notifyBtn} onClick={() => navigate('/notifications')}> </div>
🔔 <button onClick={() => navigate('/notifications')} className={styles.notifyBtn}>
{unreadCount > 0 && <Badge count={unreadCount} />} 🔔{unreadCount > 0 && <span style={{ position: 'absolute', top: -2, right: -2, width: 16, height: 16, borderRadius: 8, background: '#EF4444', color: '#fff', fontSize: 10, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 600 }}>{unreadCount}</span>}
</button> </button>
} </div>
/>
{/* Health Overview */} {/* Health Overview */}
{bpStats?.latest && hrStats?.latest ? ( <Card className={styles.overviewCard}>
<Card className={styles.overviewCard}> <div className={styles.overviewHeader}>
<div className={styles.overviewHeader}> <span className={styles.overviewTitle}></span>
<span className={styles.overviewTitle}></span> <span className={styles.overviewTime}></span>
<span className={styles.overviewTime}></span> </div>
</div> <div className={styles.overviewData}>
<div className={styles.overviewData}> <div className={styles.bpSection}>
<div className={styles.bpSection}> <span className={styles.dataLabel}></span>
<span className={styles.dataLabel}></span> {systolic ? (
<div className={styles.bpValues}> <div className={styles.bpValues}>
<span className={`${styles.bpNum} ${styles[`risk_${riskLevel}`] || ''}`}> <span className={`${styles.bpNum} ${riskLevel === 'normal' ? '' : riskLevel === 'borderline' ? styles.riskBp : styles.riskAbnormal}`}>
{systolic} {systolic}
</span> </span>
<span className={styles.bpSep}>/</span> <span className={styles.bpSep}>/</span>
<span className={`${styles.bpNum} ${styles[`risk_${riskLevel}`] || ''}`}> <span className={`${styles.bpNum} ${riskLevel === 'normal' ? '' : riskLevel === 'borderline' ? styles.riskBp : styles.riskAbnormal}`}>
{diastolic} {diastolic}
</span> </span>
</div> </div>
<span className={styles.unit}>mmHg</span> ) : <span className={styles.bpNum} style={{ fontSize: 28, opacity: 0.4 }}>--/--</span>}
</div> <span className={styles.unit}>mmHg</span>
<div className={styles.divider} />
<div className={styles.hrSection}>
<span className={styles.dataLabel}></span>
<span className={styles.hrNum}>{Number(hrStats.latest.value)}</span>
<span className={styles.unit}>bpm</span>
</div>
</div> </div>
</Card> <div className={styles.divider} />
) : ( <div className={styles.hrSection}>
<Empty icon="💓" message="暂无健康数据" /> <span className={styles.dataLabel}></span>
)} <span className={styles.hrNum}>{hrStats?.latest ? Number(hrStats.latest.value) : '--'}</span>
<span className={styles.unit}>bpm</span>
</div>
<div className={styles.divider} />
<div className={styles.hrSection}>
<span className={styles.dataLabel}></span>
<span className={styles.hrNum}>{sugarStats?.latest ? Number(sugarStats.latest.value) : '--'}</span>
<span className={styles.unit}>mmol/L</span>
</div>
<div className={styles.divider} />
<div className={styles.hrSection}>
<span className={styles.dataLabel}></span>
<span className={styles.hrNum}>{spo2Stats?.latest ? Number(spo2Stats.latest.value) : '--'}</span>
<span className={styles.unit}>%</span>
</div>
</div>
</Card>
{/* Quick Actions */} {/* Quick Actions */}
<div className={styles.quickActions}> <div className={styles.quickActions}>
{QUICK_ACTIONS.map((action) => ( {QUICK_ACTIONS.map((action) => (
<button <button key={action.key} className={styles.quickAction} onClick={() => navigate(action.path)}>
key={action.label} <span className={styles.quickIcon}>
className={styles.quickAction} {action.icon}
onClick={() => navigate(action.path)} {action.badge && unreadCount > 0 && (
> <span style={{
<span className={styles.quickIcon}>{action.icon}</span> position: 'absolute', top: -4, right: -8, minWidth: 18, height: 18,
borderRadius: 9, background: '#EF4444', color: '#fff', fontSize: 10,
display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 600,
}}>{unreadCount}</span>
)}
</span>
<span className={styles.quickLabel}>{action.label}</span> <span className={styles.quickLabel}>{action.label}</span>
</button> </button>
))} ))}
</div> </div>
{/* Health Tip */}
<Card
className={styles.tipCard}
onClick={() => setTipIndex((prev) => (prev + 1) % HEALTH_TIPS.length)}
>
<div className={styles.tipHeader}>
<span>💡</span>
<span className={styles.tipTitle}></span>
<span className={styles.tipHint}></span>
</div>
<p className={styles.tipContent}>{HEALTH_TIPS[tipIndex]}</p>
</Card>
</div> </div>
); );
} }

View File

@@ -1,33 +1,60 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/layout/PageHeader';
import { Card } from '@/components/common/Card'; import { Card } from '@/components/common/Card';
import { Button } from '@/components/common/Button'; import { Button } from '@/components/common/Button';
import { PieChart } from '@/components/charts/PieChart'; import { ToastContainer, toast } from '@/components/common/Toast';
import * as medicationService from '@/services/medication.service'; import * as medicationService from '@/services/medication.service';
import type { Medication, MedicationAdherence } from '@/types'; import type { Medication, MedicationRecord } from '@/types';
import styles from './MedicationDetailPage.module.css'; import styles from './MedicationDetailPage.module.css';
export function MedicationDetailPage() { export function MedicationDetailPage() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const [med, setMed] = useState<Medication | null>(null);
const [medications, setMedications] = useState<Medication[]>([]); const [records, setRecords] = useState<MedicationRecord[]>([]);
const [adherence, setAdherence] = useState<MedicationAdherence | null>(null); const [loading, setLoading] = useState(false);
useEffect(() => { const load = () => {
medicationService.getMedications().then(setMedications); if (!id) return;
if (id) medicationService.getAdherence(id).then(setAdherence).catch(() => {}); medicationService.getMedications().then((meds) => {
}, [id]); const m = meds.find((x) => x.id === id);
if (m) setMed(m);
});
medicationService.getMedicationRecords(id).then(setRecords);
};
const med = medications.find((m) => m.id === id); useEffect(() => { load(); }, [id]);
const today = new Date().toISOString().split('T')[0];
const todayRecords = records.filter((r) => r.takenAt?.startsWith(today) || !r.takenAt);
const todayTaken = todayRecords.filter((r) => r.isTaken);
const todaySlots = med?.timeSlots || [];
const handleMarkTaken = async (slot: string) => {
if (!id) return;
setLoading(true);
try {
await medicationService.markTaken(id, slot);
toast('已记录');
load();
} catch { toast('失败', 'error'); }
finally { setLoading(false); }
};
if (!med) { if (!med) {
return ( return <div className="page--no-tab"><PageHeader title="药品详情" /><div style={{ padding: 40, textAlign: 'center', color: '#9CA3AF' }}></div></div>;
<div className="page--no-tab"> }
<PageHeader title="药品详情" />
<div style={{ padding: 40, textAlign: 'center', color: '#9CA3AF' }}></div> const slotTaken = (slot: string) => todayRecords.some((r) => r.timeSlot === slot && r.isTaken);
</div>
); // Recent 7 days adherence
const last7Days: { date: string; taken: number; total: number }[] = [];
for (let i = 6; i >= 0; i--) {
const d = new Date();
d.setDate(d.getDate() - i);
const ds = d.toISOString().split('T')[0];
const dayRecords = records.filter((r) => r.takenAt?.startsWith(ds) && r.isTaken);
last7Days.push({ date: ds, taken: dayRecords.length, total: todaySlots.length });
} }
return ( return (
@@ -36,24 +63,69 @@ export function MedicationDetailPage() {
<Card className={styles.infoCard}> <Card className={styles.infoCard}>
<div className={styles.infoTitle}>{med.drugName}</div> <div className={styles.infoTitle}>{med.drugName}</div>
<div className={styles.infoRow}><span></span><span>{med.dosage}</span></div> <div className={styles.infoRow}><span></span><span>{med.dosage}</span></div>
<div className={styles.infoRow}><span></span><span>{med.timeSlots.join(', ')}</span></div> <div className={styles.infoRow}><span></span><span>{med.frequency} · {med.timeSlots.join(', ')}</span></div>
<div className={styles.infoRow}><span></span><span>{med.startDate} ~ {med.endDate || '长期'}</span></div> <div className={styles.infoRow}><span></span><span>{med.startDate} ~ {med.endDate || '长期'}</span></div>
<div className={styles.infoRow}><span></span><span className={med.status === 'active' ? styles.activeBadge : ''}>{med.status === 'active' ? '进行中' : '已结束'}</span></div>
{med.notes && <div className={styles.infoRow}><span></span><span>{med.notes}</span></div>} {med.notes && <div className={styles.infoRow}><span></span><span>{med.notes}</span></div>}
</Card> </Card>
{adherence && ( {/* Today's medication tracking */}
<Card className={styles.adherenceCard}> <Card className={styles.infoCard}>
<div className={styles.adherenceTitle}>30</div> <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>
<div className={styles.adherenceRate}>{adherence.rate}%</div> <span style={{ color: '#9CA3AF', fontWeight: 400, fontSize: 13 }}>{today}</span>
<PieChart </div>
data={[ {todaySlots.map((slot) => {
{ name: '已服用', value: adherence.rate, color: '#10B981' }, const taken = slotTaken(slot);
{ name: '未服用', value: 100 - adherence.rate, color: '#EF4444' }, return (
]} <div key={slot} style={{
/> display: 'flex', alignItems: 'center', justifyContent: 'space-between',
</Card> padding: '10px 0', borderBottom: '1px solid #f0f0f0',
)} }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{
width: 10, height: 10, borderRadius: 5,
background: taken ? '#10B981' : '#E5E7EB',
}} />
<span style={{ fontSize: 14 }}>{slot}</span>
<span style={{ fontSize: 12, color: taken ? '#10B981' : '#9CA3AF' }}>
{taken ? '已服用' : '未服用'}
</span>
</div>
{!taken && med.status === 'active' && (
<Button size="sm" variant="outline" loading={loading} onClick={() => handleMarkTaken(slot)}>
</Button>
)}
</div>
);
})}
<div style={{ marginTop: 12, fontSize: 12, color: '#9CA3AF' }}>
{todaySlots.filter((s) => slotTaken(s)).length}/{todaySlots.length}
</div>
</Card>
{/* 7-day adherence */}
<Card className={styles.infoCard}>
<div style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>7</div>
<div style={{ display: 'flex', gap: 4 }}>
{last7Days.map((d) => {
const pct = d.total > 0 ? (d.taken / d.total) * 100 : 0;
return (
<div key={d.date} style={{ flex: 1, textAlign: 'center' }}>
<div style={{
height: 40, borderRadius: 6, marginBottom: 4,
background: pct === 100 ? '#10B981' : pct > 0 ? '#F59E0B' : '#E5E7EB',
transition: 'background 0.3s',
}} />
<div style={{ fontSize: 10, color: '#9CA3AF' }}>{d.date.slice(5)}</div>
</div>
);
})}
</div>
<div style={{ display: 'flex', gap: 12, marginTop: 8, fontSize: 11, color: '#9CA3AF' }}>
<span>🟢 </span><span>🟡 </span><span> </span>
</div>
</Card>
<ToastContainer />
</div> </div>
); );
} }

View File

@@ -11,21 +11,27 @@ export function FollowUpEditPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [description, setDescription] = useState(''); const [description, setDescription] = useState('');
const [doctorName, setDoctorName] = useState(''); const [notes, setNotes] = useState('');
const [scheduledAt, setScheduledAt] = useState(''); const [scheduledAt, setScheduledAt] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleSubmit = async () => { const handleSubmit = async () => {
if (!title) { toast('请填写标题', 'error'); return; } if (!title) { toast('请填写标题', 'error'); return; }
setLoading(true); setLoading(true);
await followupService.addFollowUp({ try {
title, description, await followupService.addFollowUp({
scheduledAt: scheduledAt || new Date().toISOString(), title, description, notes,
status: 'upcoming', scheduledAt: scheduledAt || new Date().toISOString(),
reminderEnabled: true, status: 'upcoming',
}); reminderEnabled: true,
toast('添加成功'); });
navigate(-1); toast('添加成功');
navigate(-1);
} catch {
toast('添加失败', 'error');
} finally {
setLoading(false);
}
}; };
return ( return (
@@ -33,12 +39,11 @@ export function FollowUpEditPage() {
<PageHeader title="新增复查" /> <PageHeader title="新增复查" />
<div className={styles.form}> <div className={styles.form}>
<Input label="复查标题" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="如PCI术后3个月复查" /> <Input label="复查标题" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="如PCI术后3个月复查" />
<Input label="医生" value={doctorName} onChange={(e) => setDoctorName(e.target.value)} placeholder="医生姓名" /> <Input label="复查描述" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="简单描述复查目的" />
<Input label="描述" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="复查描述" />
<Input label="复查时间" value={scheduledAt} onChange={(e) => setScheduledAt(e.target.value)} type="datetime-local" /> <Input label="复查时间" value={scheduledAt} onChange={(e) => setScheduledAt(e.target.value)} type="datetime-local" />
<div className={styles.textareaWrap}> <div className={styles.textareaWrap}>
<label className={styles.label}></label> <label className={styles.label}></label>
<textarea className={styles.textarea} value={description} onChange={(e) => setDescription(e.target.value)} placeholder="复查说明..." rows={3} /> <textarea className={styles.textarea} value={notes} onChange={(e) => setNotes(e.target.value)} placeholder="其他补充说明..." rows={3} />
</div> </div>
<Button variant="primary" size="lg" fullWidth loading={loading} onClick={handleSubmit}></Button> <Button variant="primary" size="lg" fullWidth loading={loading} onClick={handleSubmit}></Button>
</div> </div>

View File

@@ -36,7 +36,7 @@ export function FollowUpListPage() {
<Empty icon="🏥" message="暂无复查计划" /> <Empty icon="🏥" message="暂无复查计划" />
) : ( ) : (
filtered.map((f) => ( filtered.map((f) => (
<Card key={f.id} className={styles.card} onClick={() => navigate(`/health/medications`)}> <Card key={f.id} className={styles.card}>
<div className={styles.cardHeader}> <div className={styles.cardHeader}>
<span className={styles.title}>{f.title}</span> <span className={styles.title}>{f.title}</span>
<span className={styles.status} style={{ color: statusColor(f.status) }}> <span className={styles.status} style={{ color: statusColor(f.status) }}>

View File

@@ -2,63 +2,99 @@ import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { PageHeader } from '@/components/layout/PageHeader'; import { PageHeader } from '@/components/layout/PageHeader';
import { Card } from '@/components/common/Card'; import { Card } from '@/components/common/Card';
import { Button } from '@/components/common/Button';
import { ToastContainer, toast } from '@/components/common/Toast';
import * as reportService from '@/services/report.service';
import type { Report } from '@/types';
import { formatDate } from '@/utils/format'; import { formatDate } from '@/utils/format';
import * as reportService from '@/services/report.service';
import styles from './ReportDetailPage.module.css'; import styles from './ReportDetailPage.module.css';
interface ReportData {
id: string; title: string; category: string; status: string;
imageUrls: string[]; riskLevel?: string; summary?: string; suggestions?: string;
createdAt: string; completedAt?: string; patientName?: string; doctorName?: string;
items?: { id: string; itemName: string; resultValue: string; unit?: string; referenceRange?: string; isAbnormal: boolean }[];
}
export function ReportDetailPage() { export function ReportDetailPage() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [report, setReport] = useState<Report | null>(null); const [report, setReport] = useState<ReportData | null>(null);
useEffect(() => { useEffect(() => {
if (id) reportService.getReport(id).then((r) => setReport(r || null)); if (id) {
import('@/services/api-client').then(({ api }) => {
api.get<ReportData>(`/api/reports/${id}`).then((r) => setReport(r.data));
});
}
}, [id]); }, [id]);
if (!report) { if (!report) return <div className="page--no-tab"><PageHeader title="报告详情" /><div style={{ padding: 40, textAlign: 'center', color: '#9CA3AF' }}>...</div></div>;
return <div className="page--no-tab"><PageHeader title="报告详情" /><div style={{ padding: 40, textAlign: 'center', color: '#9CA3AF' }}></div></div>;
}
const riskLabels = { normal: '正常', attention: '需关注', abnormal: '异常' }; const riskMap: Record<string, { label: string; color: string }> = {
const riskColors = { normal: '#10B981', attention: '#F59E0B', abnormal: '#EF4444' }; normal: { label: '正常', color: '#10B981' },
attention: { label: '需关注', color: '#F59E0B' },
abnormal: { label: '异常', color: '#EF4444' },
};
const isCompleted = report.status === 'completed';
return ( return (
<div className="page--no-tab"> <div className="page--no-tab">
<PageHeader title={report.title} /> <PageHeader title={report.title} />
<Card className={styles.card}> <Card className={styles.card}>
<div className={styles.infoRow}><span></span><span>{report.category}</span></div> <div className={styles.infoRow}><span></span><span>{report.category}</span></div>
<div className={styles.infoRow}><span></span><span>{formatDate(report.uploadAt)}</span></div> <div className={styles.infoRow}><span></span><span>{formatDate(report.createdAt)}</span></div>
<div className={styles.infoRow}><span></span><span>{report.status === 'completed' ? '✅ 已解读' : report.status === 'interpreting' ? '⏳ 解读中' : '📤 待解读'}</span></div> <div className={styles.infoRow}>
{report.result && ( <span></span>
<div className={styles.result}> <span style={{ color: isCompleted ? '#10B981' : '#F59E0B' }}>
<div className={styles.riskBadge} style={{ background: riskColors[report.result.riskLevel] }}> {isCompleted ? '已完成解读' : '待解读'}
{riskLabels[report.result.riskLevel]} </span>
</div> </div>
<p className={styles.summary}>{report.result.summary}</p>
<div className={styles.findingsTitle}></div> {report.imageUrls && report.imageUrls.length > 0 && (
{report.result.findings.map((f, i) => ( <div style={{ marginTop: 12 }}>
<div key={i} className={styles.finding}> <div style={{ fontSize: 13, fontWeight: 500, marginBottom: 8 }}></div>
<div className={styles.findingHeader}> <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<span className={styles.findingItem}>{f.item}</span> {report.imageUrls.map((url, i) => (
<span className={`${styles.findingValue} ${f.assessment === 'abnormal' ? styles.abnormal : ''}`}>{f.value}</span> <img key={i} src={`http://localhost:5000${url}`} alt="report"
</div> style={{ width: 80, height: 80, borderRadius: 8, objectFit: 'cover', border: '1px solid #eee' }} />
<div className={styles.findingRef}>{f.referenceRange}</div>
</div>
))}
<div className={styles.findingsTitle}></div>
<ul className={styles.suggestions}>
{report.result.suggestions.map((s, i) => (
<li key={i}>{s}</li>
))} ))}
</ul> </div>
</div>
)}
{isCompleted && (
<div className={styles.result}>
{report.riskLevel && (
<div className={styles.riskBadge} style={{ background: riskMap[report.riskLevel]?.color || '#888' }}>
{riskMap[report.riskLevel]?.label || report.riskLevel}
</div>
)}
<p className={styles.summary}>{report.summary}</p>
{report.items && report.items.length > 0 && (
<>
<div className={styles.findingsTitle}></div>
{report.items.map((item) => (
<div key={item.id} className={styles.finding}>
<div className={styles.findingHeader}>
<span className={styles.findingItem}>{item.itemName}</span>
<span className={styles.findingValue} style={{ color: item.isAbnormal ? '#EF4444' : '#10B981' }}>
{item.resultValue} {item.unit || ''}
</span>
</div>
<div className={styles.findingRef}>{item.referenceRange || '-'}</div>
</div>
))}
</>
)}
{report.suggestions && (
<>
<div className={styles.findingsTitle}></div>
<p style={{ fontSize: 13, color: '#555', lineHeight: 1.6 }}>{report.suggestions}</p>
</>
)}
</div> </div>
)} )}
</Card> </Card>
<ToastContainer />
</div> </div>
); );
} }

View File

@@ -38,13 +38,13 @@ export async function getFollowUps(): Promise<FollowUp[]> {
return res.data.map(mapFollowUp); return res.data.map(mapFollowUp);
} }
export async function addFollowUp(data: Omit<FollowUp, 'id' | 'patientId' | 'createdAt'>): Promise<FollowUp> { export async function addFollowUp(data: Omit<FollowUp, 'id' | 'patientId' | 'createdAt'> & { notes?: string }): Promise<FollowUp> {
const res = await api.post<RawFollowUp>('/api/follow-ups', { const res = await api.post<RawFollowUp>('/api/follow-ups', {
title: data.title, title: data.title,
description: data.description, description: data.description,
scheduledAt: data.scheduledAt, scheduledAt: data.scheduledAt,
reminderEnabled: data.reminderEnabled, reminderEnabled: data.reminderEnabled,
notes: data.notes, notes: (data as Record<string, unknown>).notes || null,
}); });
return mapFollowUp(res.data); return mapFollowUp(res.data);
} }