Refine homepage to match dashboard reference

This commit is contained in:
2569718930@qq.com
2026-04-23 03:16:30 +08:00
parent 81f7e62ac5
commit 558a0031be
4 changed files with 510 additions and 179 deletions
@@ -238,21 +238,36 @@ type HomeForecastDay = {
maxTemp: number;
};
type HomeSummaryMetric = {
type HomeSummaryCard = {
key: string;
label: string;
value: string;
accent?: "green" | "amber" | "red" | "cyan";
title: string;
linkLabel?: string;
items: Array<{
label: string;
value: string;
accent?: "green" | "amber" | "red" | "cyan";
}>;
};
function buildDashboardSummaryMetrics(
function buildDashboardSummaryCards(
snapshots: CitySnapshot[],
locale: string,
): HomeSummaryMetric[] {
): HomeSummaryCard[] {
const topTradable = snapshots.filter((snapshot) => snapshot.tradableOpportunity);
const highCount = snapshots.filter((snapshot) => snapshot.city.deb_recent_tier === "high").length;
const mediumCount = snapshots.filter((snapshot) => snapshot.city.deb_recent_tier === "medium").length;
const lowCount = snapshots.filter((snapshot) => snapshot.city.deb_recent_tier === "low").length;
const strongAgreement = snapshots.filter(
(snapshot) => Number(snapshot.city.deb_recent_hit_rate ?? 0) >= 0.66,
).length;
const mediumAgreement = snapshots.filter((snapshot) => {
const rate = Number(snapshot.city.deb_recent_hit_rate ?? 0);
return rate >= 0.4 && rate < 0.66;
}).length;
const weakAgreement = Math.max(
snapshots.length - strongAgreement - mediumAgreement,
0,
);
const avgEdge = topTradable.length
? topTradable.reduce((sum, snapshot) => {
const edgeValue = Number(getMarketEdgeValue(snapshot.detail));
@@ -266,28 +281,92 @@ function buildDashboardSummaryMetrics(
return [
{
key: "high",
label: locale === "en-US" ? "High risk" : "高风险",
value: String(highCount),
accent: "red",
key: "opportunities",
title: locale === "en-US" ? "Today's Opportunities" : "今日机会分布",
items: [
{
label: locale === "en-US" ? "High risk" : "高风险",
value: String(highCount),
accent: "red",
},
{
label: locale === "en-US" ? "Medium risk" : "中风险",
value: String(mediumCount),
accent: "amber",
},
{
label: locale === "en-US" ? "Low risk" : "低风险",
value: String(lowCount),
accent: "green",
},
],
},
{
key: "medium",
label: locale === "en-US" ? "Medium risk" : "中风险",
value: String(mediumCount),
accent: "amber",
key: "market-summary",
title: locale === "en-US" ? "Market Summary" : "市场概览",
items: [
{
label: locale === "en-US" ? "Total markets" : "总市场数",
value: String(snapshots.length),
},
{
label: locale === "en-US" ? "Active" : "活跃市场",
value: String(topTradable.length),
accent: "cyan",
},
{
label: locale === "en-US" ? "Avg. edge" : "平均优势",
value: formatEdge(avgEdge),
accent: "green",
},
],
},
{
key: "low",
label: locale === "en-US" ? "Low risk" : "低风险",
value: String(lowCount),
accent: "green",
key: "model-agreement",
title: locale === "en-US" ? "Model Agreement" : "模型一致性",
items: [
{
label: locale === "en-US" ? "High" : "高",
value: `${strongAgreement}`,
accent: "green",
},
{
label: locale === "en-US" ? "Medium" : "中",
value: `${mediumAgreement}`,
accent: "amber",
},
{
label: locale === "en-US" ? "Low" : "低",
value: `${weakAgreement}`,
accent: "red",
},
],
},
{
key: "edge",
label: locale === "en-US" ? "Avg. edge" : "平均优势",
value: formatEdge(avgEdge),
accent: "cyan",
key: "impact-window",
title: locale === "en-US" ? "High Impact Window" : "高影响窗口",
items: [
{
label: locale === "en-US" ? "Next 6 hours" : "未来 6 小时",
value:
locale === "en-US"
? `${Math.min(topTradable.length, 12)} markets`
: `${Math.min(topTradable.length, 12)} 个市场`,
},
{
label: locale === "en-US" ? "Focus city" : "焦点城市",
value: topTradable[0]?.city.display_name || "--",
accent: "cyan",
},
{
label: locale === "en-US" ? "Best edge" : "最佳优势",
value:
topTradable.length > 0
? formatEdge(getMarketEdgeValue(topTradable[0]?.detail))
: "--",
accent: "green",
},
],
},
];
}
@@ -295,29 +374,50 @@ function buildDashboardSummaryMetrics(
function HomeMapToolbar() {
const { locale } = useI18n();
return (
<div className="home-map-toolbar" aria-label={locale === "en-US" ? "Map layer controls" : "地图图层控件"}>
<div className="home-map-modes">
<button type="button" className="active">
<Thermometer size={14} strokeWidth={2} />
<span>{locale === "en-US" ? "Temperature" : "温度"}</span>
</button>
<button type="button">
<CloudRain size={14} strokeWidth={2} />
<span>{locale === "en-US" ? "Precipitation" : "降水"}</span>
</button>
<button type="button">
<Wind size={14} strokeWidth={2} />
<span>{locale === "en-US" ? "Wind" : "风场"}</span>
<>
<div className="home-map-header">
<div className="home-map-title">
<strong>{locale === "en-US" ? "Global weather regime" : "全球天气态势"}</strong>
<span>{locale === "en-US" ? "Live monitored weather derivatives board" : "天气衍生品实时监控面板"}</span>
</div>
</div>
<div className="home-map-toolbar" aria-label={locale === "en-US" ? "Map layer controls" : "地图图层控件"}>
<div className="home-map-modes">
<button type="button" className="active">
<Thermometer size={14} strokeWidth={2} />
<span>{locale === "en-US" ? "Temperature" : "温度"}</span>
</button>
<button type="button">
<CloudRain size={14} strokeWidth={2} />
<span>{locale === "en-US" ? "Precipitation" : "降水"}</span>
</button>
<button type="button">
<Wind size={14} strokeWidth={2} />
<span>{locale === "en-US" ? "Wind" : "风场"}</span>
</button>
</div>
<button
type="button"
className="home-map-expand"
aria-label={locale === "en-US" ? "Expand map" : "放大地图"}
>
<Expand size={15} strokeWidth={2} />
</button>
</div>
<button
type="button"
className="home-map-expand"
aria-label={locale === "en-US" ? "Expand map" : "放大地图"}
>
<Expand size={15} strokeWidth={2} />
</button>
</div>
<div className="home-map-legend" aria-hidden="true">
<span>{locale === "en-US" ? "Temperature (°F)" : "温度 (°F)"}</span>
<div className="home-map-legend-bar" />
<div className="home-map-legend-values">
<span>-4</span>
<span>14</span>
<span>32</span>
<span>50</span>
<span>68</span>
<span>86</span>
<span>104</span>
</div>
</div>
</>
);
}
@@ -771,6 +871,15 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
×
</button>
<div className="home-panel-header">
<span className="home-focus-title">
{locale === "en-US" ? "Focus city" : "焦点城市"}
</span>
<button type="button" className="home-why-link">
{locale === "en-US" ? "Why this city?" : "为什么是它?"}
</button>
</div>
<div className="home-top-opportunity-label">
{showOpportunityLabel
? locale === "en-US"
@@ -1052,8 +1161,8 @@ function OpportunityStrip({
}) {
const { locale } = useI18n();
const store = useDashboardStore();
const summaryMetrics = useMemo(
() => buildDashboardSummaryMetrics(snapshots, locale),
const summaryCards = useMemo(
() => buildDashboardSummaryCards(snapshots, locale),
[locale, snapshots],
);
const items = useMemo(
@@ -1069,10 +1178,22 @@ function OpportunityStrip({
aria-label={locale === "en-US" ? "Opportunity strip" : "机会条"}
>
<div className="home-summary-grid">
{summaryMetrics.map((metric) => (
<div key={metric.key} className={clsx("home-summary-card", metric.accent && `accent-${metric.accent}`)}>
<span>{metric.label}</span>
<strong>{metric.value}</strong>
{summaryCards.map((card) => (
<div key={card.key} className="home-summary-card">
<div className="home-summary-card-head">
<strong>{card.title}</strong>
<span>{locale === "en-US" ? "View all" : "查看全部"}</span>
</div>
<div className="home-summary-card-body">
{card.items.map((item) => (
<div key={`${card.key}-${item.label}`} className="home-summary-stat">
<b className={item.accent ? `accent-${item.accent}` : undefined}>
{item.value}
</b>
<span>{item.label}</span>
</div>
))}
</div>
</div>
))}
</div>
@@ -1118,7 +1239,10 @@ function OpportunityStrip({
{locale === "en-US" ? "target" : "目标"}
</span>
<div className="opportunity-card-footer">
<span className="opportunity-yes">YES {formatCents(detail?.market_scan?.yes_buy)}</span>
<div className="opportunity-price-pair">
<span className="opportunity-yes">YES {formatCents(detail?.market_scan?.yes_buy)}</span>
<span className="opportunity-no">NO {formatCents(detail?.market_scan?.no_buy)}</span>
</div>
<span className="opportunity-edge">{formatEdge(getMarketEdgeValue(detail))}</span>
</div>
</button>