import { useEffect, useState, useCallback } from 'react'; import { LoadingState } from '@/components/ui'; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; import { FileText, Download, Activity, TrendingUp, TrendingDown, AlertTriangle, ChevronLeft, RefreshCw } from 'lucide-react'; import { useReportsStore } from '@/stores/reportsStore'; import { useDiseaseStore } from '@/stores/diseaseStore'; import { ErrorBanner } from '@/components/ErrorBanner'; import { DiseaseFilter } from '@/components/DiseaseFilter'; import type { ReportResponse } from '@/types'; const TYPE_LABELS: Record = { daily: '日报', weekly: '周报', monthly: '月报', custom: '自定义', }; const TYPE_COLORS: Record = { daily: 'bg-blue-100 text-blue-700', weekly: 'bg-purple-100 text-purple-700', monthly: 'bg-green-100 text-green-700', custom: 'bg-gray-100 text-gray-700', }; const PRIORITY_COLORS: Record = { high: 'bg-red-100 text-red-700 border-red-300', medium: 'bg-yellow-100 text-yellow-700 border-yellow-300', low: 'bg-gray-100 text-gray-600 border-gray-300', }; function downloadCSV(report: ReportResponse) { const BOM = ''; const headers = ['报告ID', '标题', '类型', '报告日期', '周期开始', '周期结束', '总病例数', '平均风险', '峰值风险日期', '峰值风险值', '高风险区域数', '趋势方向', '诊断名称', '门诊病例', '住院病例', '诊断总病例']; const { metadata, summary } = report; const breakdowns = report.diagnosis_breakdown || []; const typeLabel = TYPE_LABELS[metadata.type] || metadata.type; let csv = BOM + headers.join(',') + '\n'; if (breakdowns.length === 0) { csv += [ metadata.report_id, `"${metadata.title}"`, typeLabel, metadata.generated_at, metadata.period_start, metadata.period_end, summary.total_cases, summary.avg_risk, summary.peak_risk_date, summary.peak_risk_value, summary.high_risk_areas, summary.trend_direction, '', '', '', '' ].join(','); } else { breakdowns.forEach((d) => { csv += [ metadata.report_id, `"${metadata.title}"`, typeLabel, metadata.generated_at, metadata.period_start, metadata.period_end, summary.total_cases, summary.avg_risk, summary.peak_risk_date, summary.peak_risk_value, summary.high_risk_areas, summary.trend_direction, `"${d.diagnosis}"`, d.outpatient, d.inpatient, d.total ].join(',') + '\n'; }); } const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `${report.metadata.report_id}.csv`; link.click(); URL.revokeObjectURL(url); } function ReportList({ onSelect }: { onSelect: (id: string) => void }) { const reports = useReportsStore((s) => s.reports); const isLoading = useReportsStore((s) => s.isLoading); const error = useReportsStore((s) => s.error); const clearError = useReportsStore((s) => s.clearError); const fetchReportsList = useReportsStore((s) => s.fetchReportsList); const [filter, setFilter] = useState('all'); useEffect(() => { fetchReportsList(filter === 'all' ? undefined : filter); }, [filter, fetchReportsList]); const filters = [ { key: 'all', label: '全部' }, { key: 'daily', label: '日报' }, { key: 'weekly', label: '周报' }, { key: 'monthly', label: '月报' }, ]; if (error) return { clearError(); fetchReportsList(filter === 'all' ? undefined : filter); }} onDismiss={clearError} />; return (
{filters.map((f) => ( ))}
{isLoading ? ( ) : reports.length === 0 ? (

暂无报告

) : (
{reports.map((r) => ( onSelect(r.report_id)} className="border-b border-gray-100 hover:bg-blue-50 cursor-pointer transition-colors" > ))}
报告ID 标题 类型 报告周期 生成时间
{r.report_id} {r.title} {TYPE_LABELS[r.type] || r.type} {r.period_start} ~ {r.period_end} {r.generated_at?.slice(0, 10)}
)}
); } function ReportDetail({ reportId, onBack }: { reportId: string; onBack: () => void }) { const currentReport = useReportsStore((s) => s.currentReport); const isLoading = useReportsStore((s) => s.isLoading); const error = useReportsStore((s) => s.error); const clearError = useReportsStore((s) => s.clearError); const fetchReport = useReportsStore((s) => s.fetchReport); const { selectedDiagnoses } = useDiseaseStore(); useEffect(() => { // Skip refetch if the report is already loaded (e.g. just generated) to avoid clobbering it. if (useReportsStore.getState().currentReport?.metadata?.report_id !== reportId) { fetchReport(reportId); } }, [reportId]); if (error) return { clearError(); fetchReport(reportId); }} onDismiss={clearError} />; if (isLoading || !currentReport) return
加载报告...
; const { metadata, summary, sections, recommendations, diagnosis_breakdown } = currentReport; const filteredBreakdown = diagnosis_breakdown?.filter( d => selectedDiagnoses.length === 0 || selectedDiagnoses.includes(d.diagnosis) ) || []; return (

{metadata.title}

{TYPE_LABELS[metadata.type] || metadata.type} {metadata.period_start} ~ {metadata.period_end} 生成: {metadata.generated_at?.slice(0, 10)}
{/* Summary cards */}
{[ { label: '总病例数', value: summary.total_cases.toLocaleString(), icon: Activity, color: 'text-blue-600', bg: 'bg-blue-50' }, { label: '平均风险', value: (summary.avg_risk * 100).toFixed(1) + '%', icon: AlertTriangle, color: 'text-orange-600', bg: 'bg-orange-50' }, { label: '峰值风险', value: (summary.peak_risk_value * 100).toFixed(1) + '%', icon: TrendingUp, color: 'text-red-600', bg: 'bg-red-50' }, { label: '高风险区域', value: `${summary.high_risk_areas}个`, icon: TrendingUp, color: 'text-red-600', bg: 'bg-red-50' }, { label: '趋势', value: summary.trend_direction === 'improving' ? '好转' : summary.trend_direction === 'worsening' ? '恶化' : '平稳', icon: summary.trend_direction === 'improving' ? TrendingDown : summary.trend_direction === 'worsening' ? TrendingUp : Activity, color: summary.trend_direction === 'improving' ? 'text-green-600' : summary.trend_direction === 'worsening' ? 'text-red-600' : 'text-gray-600', bg: summary.trend_direction === 'improving' ? 'bg-green-50' : summary.trend_direction === 'worsening' ? 'bg-red-50' : 'bg-gray-50', }, ].map((stat) => (
{stat.label}
{stat.value}
))}
{/* Sections and charts in 2-column layout */}
{sections.map((section, idx) => (

{section.title}

{section.content}

))}
{/* Diagnosis breakdown chart */} {filteredBreakdown.length > 0 ? (

诊断分类统计

门诊
住院
) : (

暂无诊断分类数据

)} {/* Recommendations */} {recommendations.length > 0 && (

建议措施

{recommendations.map((rec, idx) => (
{rec.priority === 'high' ? '高' : rec.priority === 'medium' ? '中' : '低'}
{rec.title}
{rec.description}
))}
)}
); } export function ReportsCenter() { const [view, setView] = useState<'list' | 'detail'>('list'); const [selectedReportId, setSelectedReportId] = useState(null); const { error, clearError, fetchReportsList, generateReport } = useReportsStore(); const [genType, setGenType] = useState('daily'); const [isGenerating, setIsGenerating] = useState(false); const handleGenerate = useCallback(async () => { setIsGenerating(true); try { await generateReport(genType); const generated = useReportsStore.getState().currentReport; if (generated) { setSelectedReportId(generated.metadata.report_id); setView('detail'); } } finally { setIsGenerating(false); } }, [genType, generateReport]); const handleSelect = useCallback((id: string) => { setSelectedReportId(id); setView('detail'); }, []); const handleBack = useCallback(() => { setView('list'); setSelectedReportId(null); fetchReportsList(); }, [fetchReportsList]); return (
{error && view === 'list' && ( { clearError(); fetchReportsList(); }} onDismiss={clearError} /> )}

报表中心

健康风险评估报告管理与导出

{view === 'list' && (
)}
{view === 'list' && } {view === 'detail' && selectedReportId && ( )}
); }