Reduce duplicate scan terminal decision copy and chrome
Removed the scan KPI bar, trimmed redundant decision-card content, and shortened the primary reason text so the city analysis cards read like concise signals instead of repeating the same state across sections. Constraint: Keep existing city decision logic and data flow unchanged while simplifying the UI Rejected: Rework the full card layout or mobile card flow | larger surface area than requested Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep hero, decision band, and market line responsibilities separate to avoid duplicate copy returning Tested: frontend npm run build Not-tested: Manual visual QA in browser across desktop/mobile breakpoints
This commit is contained in:
@@ -1,77 +0,0 @@
|
||||
import type { CityDetail, ScanOpportunityRow } from "@/lib/dashboard-types";
|
||||
import { getTodayPaceView } from "@/lib/pace-utils";
|
||||
import { formatTemperatureValue } from "@/lib/temperature-utils";
|
||||
import { getPeakWindowLabel } from "@/components/dashboard/scan-terminal/decision-utils";
|
||||
|
||||
export function AiForecastKPIBar({
|
||||
pinnedCount,
|
||||
activeCityName,
|
||||
activeDetail,
|
||||
activeRow,
|
||||
locale,
|
||||
}: {
|
||||
pinnedCount: number;
|
||||
activeCityName?: string | null;
|
||||
activeDetail?: CityDetail | null;
|
||||
activeRow?: ScanOpportunityRow | null;
|
||||
locale: string;
|
||||
}) {
|
||||
const isEn = locale === "en-US";
|
||||
const tempSymbol = activeDetail?.temp_symbol || activeRow?.temp_symbol || "°C";
|
||||
const displayName =
|
||||
activeDetail?.display_name ||
|
||||
activeRow?.city_display_name ||
|
||||
activeRow?.display_name ||
|
||||
activeCityName ||
|
||||
"--";
|
||||
const deb = activeDetail?.deb?.prediction ?? activeRow?.deb_prediction ?? null;
|
||||
const paceView = activeDetail
|
||||
? getTodayPaceView(activeDetail, locale as "zh-CN" | "en-US")
|
||||
: null;
|
||||
const peakWindow =
|
||||
paceView?.peakWindowText ||
|
||||
(activeRow ? getPeakWindowLabel(activeRow) : null) ||
|
||||
"--";
|
||||
const cards = [
|
||||
{
|
||||
label: isEn ? "Decision Cards" : "决策卡",
|
||||
value: String(pinnedCount),
|
||||
note: isEn ? "Cities opened from opportunities or map" : "从机会榜或地图加入",
|
||||
tone: "green",
|
||||
},
|
||||
{
|
||||
label: isEn ? "Current City" : "当前城市",
|
||||
value: displayName,
|
||||
note: isEn ? "City briefing stays in the right rail" : "右侧城市简报同步显示,不自动切页",
|
||||
tone: "blue",
|
||||
},
|
||||
{
|
||||
label: isEn ? "Forecast Center" : "预测中枢",
|
||||
value:
|
||||
deb != null
|
||||
? formatTemperatureValue(deb, tempSymbol, { digits: 1 })
|
||||
: "--",
|
||||
note: isEn ? "Weather center before price mapping" : "先定天气中枢,再映射价格",
|
||||
tone: "cyan",
|
||||
},
|
||||
{
|
||||
label: isEn ? "Peak Window" : "峰值窗口",
|
||||
value: peakWindow,
|
||||
note: isEn ? "Key window for daily high judgment" : "用于判断是否接近今日最高温",
|
||||
tone: "amber",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="scan-kpi-bar">
|
||||
{cards.map((card) => (
|
||||
<article key={card.label} className={`scan-kpi-card ${card.tone}`}>
|
||||
<div className="scan-kpi-label">{card.label}</div>
|
||||
<div className="scan-kpi-value">{card.value}</div>
|
||||
<div className="scan-kpi-note">{card.note}</div>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -613,15 +613,12 @@ export function AiPinnedCityCard({
|
||||
{detail && !collapsed ? (
|
||||
<div className="scan-ai-city-body" id={collapseId}>
|
||||
<WeatherDecisionBand
|
||||
currentTempText={currentTempText}
|
||||
decisionView={decisionView}
|
||||
decisionWhyText={decisionState.primaryReason}
|
||||
isEn={isEn}
|
||||
longText={localizedFinalJudgment || paceText}
|
||||
marketDecisionView={marketDecisionView}
|
||||
marketLineText={marketLineText}
|
||||
paceDeltaText={paceView?.deltaText || "--"}
|
||||
peakWindow={peakWindow}
|
||||
/>
|
||||
|
||||
<div className="scan-ai-city-analysis-grid">
|
||||
|
||||
@@ -8,25 +8,19 @@ import type {
|
||||
import { MarketDecisionLine } from "@/components/dashboard/scan-terminal/MarketDecisionLine";
|
||||
|
||||
export function WeatherDecisionBand({
|
||||
currentTempText,
|
||||
decisionView,
|
||||
decisionWhyText,
|
||||
isEn,
|
||||
longText,
|
||||
marketDecisionView,
|
||||
marketLineText,
|
||||
paceDeltaText,
|
||||
peakWindow,
|
||||
}: {
|
||||
currentTempText: string;
|
||||
decisionView: WeatherDecisionView;
|
||||
decisionWhyText: string;
|
||||
isEn: boolean;
|
||||
longText: string;
|
||||
marketDecisionView: MarketDecisionView;
|
||||
marketLineText: string;
|
||||
paceDeltaText: string;
|
||||
peakWindow: string;
|
||||
}) {
|
||||
return (
|
||||
<section className={clsx("scan-ai-decision-band", decisionView.tone)}>
|
||||
@@ -34,13 +28,6 @@ export function WeatherDecisionBand({
|
||||
<span>{decisionView.kicker}</span>
|
||||
<strong>{decisionView.action}</strong>
|
||||
<p className="scan-ai-decision-why">{decisionWhyText}</p>
|
||||
<p className="scan-ai-decision-long">{longText}</p>
|
||||
<div className="scan-ai-decision-reasons">
|
||||
{decisionView.reasons.map((reason, index) => (
|
||||
<small key={`${reason}-${index}`}>{reason}</small>
|
||||
))}
|
||||
</div>
|
||||
<p className="scan-ai-decision-risk">{decisionView.risk}</p>
|
||||
<MarketDecisionLine
|
||||
isEn={isEn}
|
||||
marketDecisionView={marketDecisionView}
|
||||
@@ -48,10 +35,6 @@ export function WeatherDecisionBand({
|
||||
/>
|
||||
</div>
|
||||
<div className="scan-ai-decision-metrics">
|
||||
<span>
|
||||
{isEn ? "Expected high" : "预计高点"}
|
||||
<b>{decisionView.expectedHigh}</b>
|
||||
</span>
|
||||
<span>
|
||||
{isEn ? "Weather range" : "天气区间"}
|
||||
<b>{decisionView.targetRange}</b>
|
||||
@@ -60,22 +43,9 @@ export function WeatherDecisionBand({
|
||||
{isEn ? "Confidence" : "信心"}
|
||||
<b>{decisionView.confidence}</b>
|
||||
</span>
|
||||
<span>
|
||||
{isEn ? "Observed" : "实测"}
|
||||
<b>{currentTempText}</b>
|
||||
</span>
|
||||
<span>
|
||||
{isEn ? "Path delta" : "路径偏差"} <b>{paceDeltaText}</b>
|
||||
</span>
|
||||
<span>
|
||||
{isEn ? "Peak window" : "峰值窗口"} <b>{peakWindow}</b>
|
||||
</span>
|
||||
<span>
|
||||
{isEn ? "Market implied" : "市场隐含"} <b>{marketDecisionView.impliedText}</b>
|
||||
</span>
|
||||
<span>
|
||||
{isEn ? "Model prob" : "模型概率"} <b>{marketDecisionView.modelText}</b>
|
||||
</span>
|
||||
<span>
|
||||
{isEn ? "Quote status" : "报价状态"}{" "}
|
||||
<b>{marketDecisionView.status === "ready" ? (isEn ? "Ready" : "已同步") : marketDecisionView.status === "loading" ? (isEn ? "Loading" : "同步中") : (isEn ? "Unavailable" : "不可用")}</b>
|
||||
|
||||
@@ -142,31 +142,31 @@ export function buildCityDecisionState({
|
||||
: "watch";
|
||||
const primaryReason = observedHighBreak
|
||||
? isEn
|
||||
? "Worth watching now: observation has broken above the model range."
|
||||
: "当前值得关注:实测已突破模型上沿。"
|
||||
? "Observation has broken above the model range."
|
||||
: "实测已突破模型上沿。"
|
||||
: peakHasPassed
|
||||
? isEn
|
||||
? "Avoid chasing now: peak window has passed; wait to confirm no new high."
|
||||
: "当前不宜追高:峰值窗口已过,等待确认是否还有新高。"
|
||||
? "Peak window has passed; confirm whether a new high can still form."
|
||||
: "峰值窗口已过,确认是否还会出现新高。"
|
||||
: observationStale
|
||||
? isEn
|
||||
? "Use as background only: observation is stale and needs the next report."
|
||||
: "当前仅作背景:观测已过旧,需要下一报文确认。"
|
||||
? "Observation is stale and needs the next report."
|
||||
: "观测已过旧,需要下一报文确认。"
|
||||
: marketStatus === "unavailable"
|
||||
? isEn
|
||||
? "Weather evidence remains usable, but no tradable quote is available yet."
|
||||
: "当前可参考天气:暂无可交易价格。"
|
||||
? "Weather evidence is usable, but no tradable quote is available yet."
|
||||
: "天气证据可参考,但暂无可交易价格。"
|
||||
: modelHighlyConsistent
|
||||
? isEn
|
||||
? "Worth watching now: models agree; wait for observation confirmation."
|
||||
: "当前值得关注:模型高度一致,等待实测确认。"
|
||||
? "Models are aligned; wait for observation confirmation."
|
||||
: "模型高度一致,等待实测确认。"
|
||||
: needsNextBulletin
|
||||
? isEn
|
||||
? "Wait for confirmation: the next bulletin should decide direction."
|
||||
: "当前建议等待:下一报文更适合决定方向。"
|
||||
? "The next bulletin is more likely to decide direction."
|
||||
: "下一报文更可能决定方向。"
|
||||
: isEn
|
||||
? "Watch the peak window and compare observations against the expected high."
|
||||
: "当前重点:盯住峰值窗口,把实测与预计高点对照。";
|
||||
? "Compare new observations with the expected high through the peak window."
|
||||
: "在峰值窗口内继续对照实测与预计高点。";
|
||||
|
||||
const badges = uniqueStatusBadges([
|
||||
observedHighBreak
|
||||
|
||||
Reference in New Issue
Block a user