feat: add analysis pages and raster risk map
Ship a new app version with broader analytics, restructured dashboards, and a server-rendered risk map. Frontend: - Add Overview, Demographic, Disease, and Environmental Health analysis pages - Add AnomalyMarkers, CalendarHeatmap, and MetricHeatmapTable components - Rebuild Alerts map onto server-rendered raster risk tiles; expand Monitoring, Trend, and District Comparison views - Extend API client, stores, and TypeScript types Backend: - Add environment router (pollutants, lag correlations) - Add risk_raster util serving XYZ 100m risk tiles - Expand cases endpoints (demographics, seasonality, diagnoses) and insights; harden auth and file-based loaders Data & tooling: - Add processed outpatient/inpatient/combined case parquet (LFS) - Add nested CLAUDE.md guides, pyrightconfig, and test updates
This commit is contained in:
@@ -148,7 +148,12 @@ function ReportDetail({ reportId, onBack }: { reportId: string; onBack: () => vo
|
||||
const fetchReport = useReportsStore((s) => s.fetchReport);
|
||||
const { selectedDiagnoses } = useDiseaseStore();
|
||||
|
||||
useEffect(() => { fetchReport(reportId); }, [reportId]);
|
||||
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 <ErrorBanner error={error} onRetry={() => { clearError(); fetchReport(reportId); }} onDismiss={clearError} />;
|
||||
if (isLoading || !currentReport) return <div className="text-center py-8 text-sm text-gray-500">加载报告...</div>;
|
||||
@@ -176,12 +181,29 @@ function ReportDetail({ reportId, onBack }: { reportId: string; onBack: () => vo
|
||||
<span>生成: {metadata.generated_at?.slice(0, 10)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => downloadCSV(currentReport)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" /> 导出CSV
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => downloadCSV(currentReport)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" /> 导出CSV
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const json = JSON.stringify(currentReport, null, 2);
|
||||
const blob = new Blob([json], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `${reportId}.json`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs bg-gray-600 text-white rounded hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" /> 导出JSON
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary cards */}
|
||||
@@ -285,7 +307,11 @@ export function ReportsCenter() {
|
||||
setIsGenerating(true);
|
||||
try {
|
||||
await generateReport(genType);
|
||||
setView('detail');
|
||||
const generated = useReportsStore.getState().currentReport;
|
||||
if (generated) {
|
||||
setSelectedReportId(generated.metadata.report_id);
|
||||
setView('detail');
|
||||
}
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user