Optimize homepage dashboard render paths
This commit is contained in:
@@ -476,130 +476,206 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
label: string;
|
label: string;
|
||||||
temperatureText: string;
|
temperatureText: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
const selectedSnapshot = store.selectedCity
|
const spotlight = useMemo(
|
||||||
? snapshots.find((snapshot) => snapshot.city.name === store.selectedCity)
|
() =>
|
||||||
: null;
|
store.selectedCity
|
||||||
const spotlight = selectedSnapshot || null;
|
? snapshots.find((snapshot) => snapshot.city.name === store.selectedCity) || null
|
||||||
|
: null,
|
||||||
|
[snapshots, store.selectedCity],
|
||||||
|
);
|
||||||
|
const spotlightView = useMemo(() => {
|
||||||
|
if (!spotlight) return null;
|
||||||
|
|
||||||
if (!spotlight) return null;
|
const { city, detail, summary } = spotlight;
|
||||||
|
const symbol = getTempSymbol(city, summary, detail);
|
||||||
const { city, detail, summary } = spotlight;
|
const currentTemp = summary?.current?.temp ?? detail?.current?.temp;
|
||||||
const symbol = getTempSymbol(city, summary, detail);
|
const debPrediction = summary?.deb?.prediction ?? detail?.deb?.prediction;
|
||||||
const currentTemp = summary?.current?.temp ?? detail?.current?.temp;
|
const maxSoFar = detail?.current?.max_so_far ?? detail?.airport_current?.max_so_far;
|
||||||
const debPrediction = summary?.deb?.prediction ?? detail?.deb?.prediction;
|
const maxTime =
|
||||||
const maxSoFar = detail?.current?.max_so_far ?? detail?.airport_current?.max_so_far;
|
detail?.current?.max_temp_time || detail?.airport_current?.max_temp_time || "--";
|
||||||
const maxTime = detail?.current?.max_temp_time || detail?.airport_current?.max_temp_time || "--";
|
const localTime = summary?.local_time || detail?.local_time || "--";
|
||||||
const localTime = summary?.local_time || detail?.local_time || "--";
|
const riskLevel =
|
||||||
const riskLevel =
|
city.deb_recent_tier ||
|
||||||
city.deb_recent_tier ||
|
city.risk_level ||
|
||||||
city.risk_level ||
|
summary?.risk?.level ||
|
||||||
summary?.risk?.level ||
|
detail?.risk?.level;
|
||||||
detail?.risk?.level;
|
const deviation = summary?.deviation_monitor || detail?.deviation_monitor;
|
||||||
const deviation = summary?.deviation_monitor || detail?.deviation_monitor;
|
const observationSource = normalizeObservationSourceLabel(
|
||||||
const observationSource =
|
|
||||||
normalizeObservationSourceLabel(
|
|
||||||
summary?.current?.settlement_source_label ||
|
summary?.current?.settlement_source_label ||
|
||||||
detail?.current?.settlement_source_label ||
|
detail?.current?.settlement_source_label ||
|
||||||
city.settlement_source_label,
|
city.settlement_source_label,
|
||||||
"METAR",
|
"METAR",
|
||||||
);
|
);
|
||||||
const probabilityBuckets =
|
const probabilityBuckets =
|
||||||
detail?.probabilities?.distribution ||
|
detail?.probabilities?.distribution ||
|
||||||
(detail?.local_date
|
(detail?.local_date
|
||||||
? detail?.multi_model_daily?.[detail.local_date]?.probabilities
|
? detail?.multi_model_daily?.[detail.local_date]?.probabilities
|
||||||
: undefined) ||
|
: undefined) ||
|
||||||
[];
|
[];
|
||||||
const displayedProbabilities = probabilityBuckets.slice(0, 5);
|
const displayedProbabilities = probabilityBuckets.slice(0, 5);
|
||||||
const modelLabels = getModelLabels(detail);
|
const modelLabels = getModelLabels(detail);
|
||||||
const marketScan = detail?.market_scan;
|
const marketScan = detail?.market_scan;
|
||||||
const hasMarketScan = Boolean(marketScan);
|
const marketBucket =
|
||||||
const showOpportunityLabel = !hasMarketScan || spotlight.tradableOpportunity;
|
marketScan?.temperature_bucket || marketScan?.top_buckets?.[0] || null;
|
||||||
const marketBucket = marketScan?.temperature_bucket || marketScan?.top_buckets?.[0] || null;
|
const yesPrice =
|
||||||
const yesPrice =
|
marketScan?.yes_buy ??
|
||||||
marketScan?.yes_buy ??
|
readNumericField(marketBucket, "yes_buy") ??
|
||||||
readNumericField(marketBucket, "yes_buy") ??
|
marketScan?.yes_token?.buy_price ??
|
||||||
marketScan?.yes_token?.buy_price ??
|
marketScan?.yes_token?.midpoint ??
|
||||||
marketScan?.yes_token?.midpoint ??
|
marketScan?.price_analysis?.yes?.ask;
|
||||||
marketScan?.price_analysis?.yes?.ask;
|
const noPrice =
|
||||||
const noPrice =
|
marketScan?.no_buy ??
|
||||||
marketScan?.no_buy ??
|
readNumericField(marketBucket, "no_buy") ??
|
||||||
readNumericField(marketBucket, "no_buy") ??
|
marketScan?.no_token?.buy_price ??
|
||||||
marketScan?.no_token?.buy_price ??
|
marketScan?.no_token?.midpoint ??
|
||||||
marketScan?.no_token?.midpoint ??
|
marketScan?.price_analysis?.no?.ask;
|
||||||
marketScan?.price_analysis?.no?.ask;
|
const marketEdge =
|
||||||
const marketEdge =
|
marketScan?.edge_percent ??
|
||||||
marketScan?.edge_percent ??
|
marketScan?.price_analysis?.yes?.edge_percent ??
|
||||||
marketScan?.price_analysis?.yes?.edge_percent ??
|
marketScan?.price_analysis?.yes?.edge ??
|
||||||
marketScan?.price_analysis?.yes?.edge ??
|
marketScan?.price_analysis?.no?.edge_percent ??
|
||||||
marketScan?.price_analysis?.no?.edge_percent ??
|
marketScan?.price_analysis?.no?.edge;
|
||||||
marketScan?.price_analysis?.no?.edge;
|
|
||||||
const marketLabel = marketBucket
|
return {
|
||||||
? getProbabilityLabel(marketBucket, symbol)
|
city,
|
||||||
: debPrediction != null
|
detail,
|
||||||
? `> ${formatTemperature(debPrediction, symbol)}`
|
tradableOpportunity: spotlight.tradableOpportunity,
|
||||||
: "--";
|
symbol,
|
||||||
const marketProbability =
|
currentTemp,
|
||||||
marketScan?.market_price ??
|
debPrediction,
|
||||||
readNumericField(marketBucket, "market_price") ??
|
maxSoFar,
|
||||||
readNumericField(marketBucket, "yes_buy") ??
|
maxTime,
|
||||||
yesPrice ??
|
localTime,
|
||||||
marketScan?.model_probability;
|
riskLevel,
|
||||||
const marketModelProbability = marketScan?.model_probability ?? marketBucket?.probability;
|
observationSource,
|
||||||
const sparklinePoints = buildSparklinePoints(marketScan?.sparkline);
|
displayedProbabilities,
|
||||||
const isLoading = store.loadingState.cityDetail && store.selectedCity === city.name;
|
modelLabels,
|
||||||
const isPro = store.proAccess.subscriptionActive;
|
marketScan,
|
||||||
const cityCode = city.icao || detail?.risk?.icao || city.airport;
|
showOpportunityLabel: !marketScan || spotlight.tradableOpportunity,
|
||||||
const localizedCityName = getLocalizedCityDisplay(city, locale, summary, detail);
|
marketLabel: marketBucket
|
||||||
const localizedAirportName = getLocalizedAirportDisplay(city, locale, detail);
|
? getProbabilityLabel(marketBucket, symbol)
|
||||||
const subtitle = `${cityCode} · ${localizedAirportName}`;
|
: debPrediction != null
|
||||||
const highRiskLabel =
|
? `> ${formatTemperature(debPrediction, symbol)}`
|
||||||
riskLevel === "high"
|
: "--",
|
||||||
? locale === "en-US"
|
yesPrice,
|
||||||
? "High risk"
|
noPrice,
|
||||||
: "高风险"
|
marketEdge,
|
||||||
: getRiskCopy(riskLevel, locale);
|
marketProbability:
|
||||||
const weatherIconKind = getHomeWeatherIconKind(detail, locale);
|
marketScan?.market_price ??
|
||||||
const trendChart = buildHomeTrendChart(detail, locale);
|
readNumericField(marketBucket, "market_price") ??
|
||||||
const debLabel = locale === "en-US" ? "DEB forecast" : "DEB 预测";
|
readNumericField(marketBucket, "yes_buy") ??
|
||||||
const dayMaxLabel = locale === "en-US" ? "24h max" : "24 小时最高";
|
yesPrice ??
|
||||||
const probabilityTitle = locale === "en-US" ? "EMOS probability" : "EMOS 概率";
|
marketScan?.model_probability,
|
||||||
const marketTitle = locale === "en-US" ? "Market edge" : "市场优势";
|
marketModelProbability: marketScan?.model_probability ?? marketBucket?.probability,
|
||||||
const marketEdgeLabel = locale === "en-US" ? "Edge" : "优势";
|
sparklinePoints: buildSparklinePoints(marketScan?.sparkline),
|
||||||
const marketImpliedLabel = locale === "en-US" ? "Implied" : "市场隐含";
|
isLoading: store.loadingState.cityDetail && store.selectedCity === city.name,
|
||||||
const marketModelLabel = locale === "en-US" ? "Model prob" : "模型概率";
|
isPro: store.proAccess.subscriptionActive,
|
||||||
const proLabel = isPro
|
cityCode: city.icao || detail?.risk?.icao || city.airport,
|
||||||
? locale === "en-US"
|
localizedCityName: getLocalizedCityDisplay(city, locale, summary, detail),
|
||||||
? "Pro signal"
|
localizedAirportName: getLocalizedAirportDisplay(city, locale, detail),
|
||||||
: "PRO 信号"
|
highRiskLabel:
|
||||||
: locale === "en-US"
|
riskLevel === "high"
|
||||||
? "Pro locked"
|
? locale === "en-US"
|
||||||
: "PRO 锁定";
|
? "High risk"
|
||||||
const forecastDays = buildHomeForecastDays(detail, locale);
|
: "高风险"
|
||||||
const keySignals = [
|
: getRiskCopy(riskLevel, locale),
|
||||||
{
|
weatherIconKind: getHomeWeatherIconKind(detail, locale),
|
||||||
active: Number(marketEdge) > 0,
|
trendChart: buildHomeTrendChart(detail, locale),
|
||||||
label:
|
debLabel: locale === "en-US" ? "DEB forecast" : "DEB 预测",
|
||||||
locale === "en-US"
|
dayMaxLabel: locale === "en-US" ? "24h max" : "24 小时最高",
|
||||||
? "DEB > Market implied"
|
probabilityTitle: locale === "en-US" ? "EMOS probability" : "EMOS 概率",
|
||||||
: "DEB 高于市场隐含",
|
marketTitle: locale === "en-US" ? "Market edge" : "市场优势",
|
||||||
tone: "green",
|
marketEdgeLabel: locale === "en-US" ? "Edge" : "优势",
|
||||||
},
|
marketImpliedLabel: locale === "en-US" ? "Implied" : "市场隐含",
|
||||||
{
|
marketModelLabel: locale === "en-US" ? "Model prob" : "模型概率",
|
||||||
active: deviation?.trend === "expanding" || deviation?.direction === "hot",
|
proLabel:
|
||||||
label: locale === "en-US" ? "Rising temps trend" : "升温趋势",
|
store.proAccess.subscriptionActive
|
||||||
tone: "green",
|
? locale === "en-US"
|
||||||
},
|
? "Pro signal"
|
||||||
{
|
: "PRO 信号"
|
||||||
active: Boolean(detail?.peak?.hours?.length),
|
|
||||||
label:
|
|
||||||
detail?.peak?.hours?.length
|
|
||||||
? `${locale === "en-US" ? "High impact window" : "高影响窗口"} ${detail.peak.hours[0]}-${detail.peak.hours[detail.peak.hours.length - 1]}`
|
|
||||||
: locale === "en-US"
|
: locale === "en-US"
|
||||||
? "High impact window pending"
|
? "Pro locked"
|
||||||
: "高影响窗口待确认",
|
: "PRO 锁定",
|
||||||
tone: "amber",
|
forecastDays: buildHomeForecastDays(detail, locale),
|
||||||
},
|
keySignals: [
|
||||||
];
|
{
|
||||||
|
active: Number(marketEdge) > 0,
|
||||||
|
label:
|
||||||
|
locale === "en-US"
|
||||||
|
? "DEB > Market implied"
|
||||||
|
: "DEB 高于市场隐含",
|
||||||
|
tone: "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: deviation?.trend === "expanding" || deviation?.direction === "hot",
|
||||||
|
label: locale === "en-US" ? "Rising temps trend" : "升温趋势",
|
||||||
|
tone: "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: Boolean(detail?.peak?.hours?.length),
|
||||||
|
label:
|
||||||
|
detail?.peak?.hours?.length
|
||||||
|
? `${locale === "en-US" ? "High impact window" : "高影响窗口"} ${detail.peak.hours[0]}-${detail.peak.hours[detail.peak.hours.length - 1]}`
|
||||||
|
: locale === "en-US"
|
||||||
|
? "High impact window pending"
|
||||||
|
: "高影响窗口待确认",
|
||||||
|
tone: "amber",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
locale,
|
||||||
|
spotlight,
|
||||||
|
store.loadingState.cityDetail,
|
||||||
|
store.proAccess.subscriptionActive,
|
||||||
|
store.selectedCity,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!spotlightView) return null;
|
||||||
|
|
||||||
|
const {
|
||||||
|
city,
|
||||||
|
detail,
|
||||||
|
symbol,
|
||||||
|
currentTemp,
|
||||||
|
debPrediction,
|
||||||
|
maxSoFar,
|
||||||
|
maxTime,
|
||||||
|
localTime,
|
||||||
|
riskLevel,
|
||||||
|
observationSource,
|
||||||
|
displayedProbabilities,
|
||||||
|
modelLabels,
|
||||||
|
marketScan,
|
||||||
|
tradableOpportunity,
|
||||||
|
showOpportunityLabel,
|
||||||
|
marketLabel,
|
||||||
|
yesPrice,
|
||||||
|
noPrice,
|
||||||
|
marketEdge,
|
||||||
|
marketProbability,
|
||||||
|
marketModelProbability,
|
||||||
|
sparklinePoints,
|
||||||
|
isLoading,
|
||||||
|
isPro,
|
||||||
|
cityCode,
|
||||||
|
localizedCityName,
|
||||||
|
localizedAirportName,
|
||||||
|
highRiskLabel,
|
||||||
|
weatherIconKind,
|
||||||
|
trendChart,
|
||||||
|
debLabel,
|
||||||
|
dayMaxLabel,
|
||||||
|
probabilityTitle,
|
||||||
|
marketTitle,
|
||||||
|
marketEdgeLabel,
|
||||||
|
marketImpliedLabel,
|
||||||
|
marketModelLabel,
|
||||||
|
proLabel,
|
||||||
|
forecastDays,
|
||||||
|
keySignals,
|
||||||
|
} = spotlightView;
|
||||||
|
const subtitle = `${cityCode} · ${localizedAirportName}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="home-intelligence-panel full" aria-label={localizedCityName}>
|
<aside className="home-intelligence-panel full" aria-label={localizedCityName}>
|
||||||
@@ -815,7 +891,7 @@ function HomeIntelligencePanel({ snapshots }: { snapshots: CitySnapshot[] }) {
|
|||||||
{marketTitle} <small>ⓘ</small>
|
{marketTitle} <small>ⓘ</small>
|
||||||
</h3>
|
</h3>
|
||||||
<span>
|
<span>
|
||||||
{hasMarketScan && !spotlight.tradableOpportunity
|
{marketScan && !tradableOpportunity
|
||||||
? locale === "en-US"
|
? locale === "en-US"
|
||||||
? "Market closed"
|
? "Market closed"
|
||||||
: "市场已结束"
|
: "市场已结束"
|
||||||
@@ -902,12 +978,18 @@ function OpportunityStrip({
|
|||||||
}) {
|
}) {
|
||||||
const store = useDashboardStore();
|
const store = useDashboardStore();
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
const items = recentCityNames
|
const snapshotByName = useMemo(
|
||||||
.map((cityName) =>
|
() => new Map(snapshots.map((snapshot) => [snapshot.city.name, snapshot])),
|
||||||
snapshots.find((snapshot) => snapshot.city.name === cityName) || null,
|
[snapshots],
|
||||||
)
|
);
|
||||||
.filter((snapshot): snapshot is CitySnapshot => snapshot != null)
|
const items = useMemo(
|
||||||
.slice(0, 4);
|
() =>
|
||||||
|
recentCityNames
|
||||||
|
.map((cityName) => snapshotByName.get(cityName) || null)
|
||||||
|
.filter((snapshot): snapshot is CitySnapshot => snapshot != null)
|
||||||
|
.slice(0, 4),
|
||||||
|
[recentCityNames, snapshotByName],
|
||||||
|
);
|
||||||
|
|
||||||
if (!items.length) return null;
|
if (!items.length) return null;
|
||||||
|
|
||||||
@@ -1022,6 +1104,10 @@ function DashboardScreen() {
|
|||||||
),
|
),
|
||||||
[store.cities, store.cityDetailsByName, store.citySummariesByName],
|
[store.cities, store.cityDetailsByName, store.citySummariesByName],
|
||||||
);
|
);
|
||||||
|
const fallbackRecentCityNames = useMemo(
|
||||||
|
() => homepageSnapshots.slice(0, 4).map((snapshot) => snapshot.city.name),
|
||||||
|
[homepageSnapshots],
|
||||||
|
);
|
||||||
const showHomepageChrome =
|
const showHomepageChrome =
|
||||||
!store.historyState.isOpen && !store.futureModalDate;
|
!store.historyState.isOpen && !store.futureModalDate;
|
||||||
|
|
||||||
@@ -1097,11 +1183,7 @@ function DashboardScreen() {
|
|||||||
<HomeIntelligencePanel snapshots={homepageSnapshots} />
|
<HomeIntelligencePanel snapshots={homepageSnapshots} />
|
||||||
<OpportunityStrip
|
<OpportunityStrip
|
||||||
snapshots={homepageSnapshots}
|
snapshots={homepageSnapshots}
|
||||||
recentCityNames={
|
recentCityNames={recentCityNames.length ? recentCityNames : fallbackRecentCityNames}
|
||||||
recentCityNames.length
|
|
||||||
? recentCityNames
|
|
||||||
: homepageSnapshots.slice(0, 4).map((snapshot) => snapshot.city.name)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
Reference in New Issue
Block a user