fix: patient report shows interpretation, medication daily tracking, followup bugs, home overview restored, doctor renamed
This commit is contained in:
@@ -1,33 +1,60 @@
|
||||
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 { Card } from '@/components/common/Card';
|
||||
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 type { Medication, MedicationAdherence } from '@/types';
|
||||
import type { Medication, MedicationRecord } from '@/types';
|
||||
import styles from './MedicationDetailPage.module.css';
|
||||
|
||||
export function MedicationDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const [medications, setMedications] = useState<Medication[]>([]);
|
||||
const [adherence, setAdherence] = useState<MedicationAdherence | null>(null);
|
||||
const [med, setMed] = useState<Medication | null>(null);
|
||||
const [records, setRecords] = useState<MedicationRecord[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
medicationService.getMedications().then(setMedications);
|
||||
if (id) medicationService.getAdherence(id).then(setAdherence).catch(() => {});
|
||||
}, [id]);
|
||||
const load = () => {
|
||||
if (!id) return;
|
||||
medicationService.getMedications().then((meds) => {
|
||||
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) {
|
||||
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 slotTaken = (slot: string) => todayRecords.some((r) => r.timeSlot === slot && r.isTaken);
|
||||
|
||||
// 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 (
|
||||
@@ -36,24 +63,69 @@ export function MedicationDetailPage() {
|
||||
<Card className={styles.infoCard}>
|
||||
<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.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 className={med.status === 'active' ? styles.activeBadge : ''}>{med.status === 'active' ? '进行中' : '已结束'}</span></div>
|
||||
{med.notes && <div className={styles.infoRow}><span>备注</span><span>{med.notes}</span></div>}
|
||||
</Card>
|
||||
|
||||
{adherence && (
|
||||
<Card className={styles.adherenceCard}>
|
||||
<div className={styles.adherenceTitle}>近30天依从性</div>
|
||||
<div className={styles.adherenceRate}>{adherence.rate}%</div>
|
||||
<PieChart
|
||||
data={[
|
||||
{ name: '已服用', value: adherence.rate, color: '#10B981' },
|
||||
{ name: '未服用', value: 100 - adherence.rate, color: '#EF4444' },
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
{/* Today's medication tracking */}
|
||||
<Card className={styles.infoCard}>
|
||||
<div style={{ fontSize: 15, fontWeight: 600, marginBottom: 12 }}>
|
||||
今日服药 <span style={{ color: '#9CA3AF', fontWeight: 400, fontSize: 13 }}>{today}</span>
|
||||
</div>
|
||||
{todaySlots.map((slot) => {
|
||||
const taken = slotTaken(slot);
|
||||
return (
|
||||
<div key={slot} style={{
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user