Frontend features:
- 报表中心 (ReportsCenter): list/detail views, diagnosis breakdown chart, CSV export
- 多级行政下钻 (AdminBreadcrumb): 湖北省→武汉市→区→街道 hierarchical drill-down
- 按病种筛选 (DiseaseFilter): multi-select diagnosis filter on monitoring + reports pages
Backend:
- Add /forecast/{days} endpoint, diagnosis filter params on cases endpoints
- Add /streets aggregation endpoint, enrich reports with real case data
- Extract shared case_loader module
Bug fixes (14):
- Fix missing /risk/forecast route (404), historyApi pointing to non-existent router
- Fix min_risk filter silently ignored in insights/hotspots
- Fix type mismatches: CaseTrendResponse, CaseStatsResponse shapes
- Fix silent .catch(() => {}) swallowing errors, fetchAlerts not clearing stale state
- Fix lru_cache caching exceptions, generateReport used cachedGet for write op
- Fix missing useEffect deps in Insights, DistrictComparison, ReportsCenter
Performance (9):
- Zustand selectors across 9 components (eliminate re-render cascades)
- Fix districtCases.sort() mutating store state, inline IIFE → memo'd component
- CaseLocationMap: React.memo, race protection, correct deps
- AlertCard: stable callbacks, TimelinePlayer: useMemo, TopNav: clock isolation
- SideNav: modules array to module scope, DiseaseFilter: memoized filter
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
|
|
interface TopNavProps {
|
|
onLogout?: () => void;
|
|
}
|
|
|
|
function Clock() {
|
|
const [time, setTime] = useState(new Date());
|
|
useEffect(() => {
|
|
const id = setInterval(() => setTime(new Date()), 1000);
|
|
return () => clearInterval(id);
|
|
}, []);
|
|
return <span>{time.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}</span>;
|
|
}
|
|
|
|
export function TopNav({ onLogout }: TopNavProps) {
|
|
|
|
return (
|
|
<nav className="h-[52px] bg-bg-card border-b border-border flex items-center px-5 fixed top-0 left-0 right-0 z-50">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-7 h-7 bg-primary rounded-md flex items-center justify-center">
|
|
<svg className="w-4 h-4 fill-white" viewBox="0 0 24 24">
|
|
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 3c1.93 0 3.5 1.57 3.5 3.5S13.93 13 12 13s-3.5-1.57-3.5-3.5S10.07 6 12 6zm7 13H5v-.23c0-.62.28-1.2.76-1.58C7.47 15.82 9.64 15 12 15s4.53.82 6.24 2.19c.48.38.76.97.76 1.58V19z"/>
|
|
</svg>
|
|
</div>
|
|
<span className="font-display font-semibold text-[15px] text-text-primary">
|
|
WuhanChildRisk
|
|
</span>
|
|
</div>
|
|
|
|
<div className="w-px h-5 bg-border ml-4 mr-4" />
|
|
|
|
<span className="text-[13px] text-text-secondary">
|
|
儿童呼吸道疾病风险监测预警平台
|
|
</span>
|
|
|
|
<div className="ml-auto flex items-center gap-5">
|
|
<span className="text-[12px] text-text-muted">
|
|
<Clock />
|
|
</span>
|
|
<div className="flex items-center gap-2 text-[13px] text-text-secondary">
|
|
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
|
</svg>
|
|
admin
|
|
</div>
|
|
{onLogout && (
|
|
<button
|
|
onClick={onLogout}
|
|
className="text-[12px] text-text-muted hover:text-danger transition-colors"
|
|
>
|
|
退出
|
|
</button>
|
|
)}
|
|
</div>
|
|
</nav>
|
|
);
|
|
}
|