Keep city decision cards extensible as product components

AiPinnedForecastView had become a mini application that owned list state, card shell, freshness, market, AI evidence, and model evidence rendering in one place. This split keeps the workspace wrapper thin and gives the decision card explicit product-component seams for future recommendation reasons, risk levels, and mobile-specific layout work.

Constraint: Preserve existing weather, market, AI read, chart, refresh, remove, collapse, and mobile behavior.

Rejected: Rewrite the decision-state construction in the same pass | this component split should stay behavior-preserving before a state-model refactor.

Confidence: high

Scope-risk: moderate

Reversibility: clean

Tested: npm run build

Not-tested: Browser visual regression on physical mobile devices.
This commit is contained in:
2569718930@qq.com
2026-04-28 09:43:20 +08:00
parent bf8de6a09b
commit de107f2673
10 changed files with 1214 additions and 1119 deletions
@@ -0,0 +1,51 @@
"use client";
import clsx from "clsx";
import type { MarketDecisionView } from "@/components/dashboard/scan-terminal/city-card-decision-utils";
export function MarketDecisionLine({
isEn,
marketDecisionView,
marketLineText,
}: {
isEn: boolean;
marketDecisionView: MarketDecisionView;
marketLineText: string;
}) {
return (
<>
<div className={clsx("scan-ai-market-mobile-line", marketDecisionView.tone)}>
<span>{isEn ? "Market price" : "市场价格"}</span>
<b>{marketLineText}</b>
</div>
<div className={clsx("scan-ai-market-decision", marketDecisionView.tone)}>
<div>
<span>{isEn ? "Polymarket price layer" : "Polymarket 价格层"}</span>
<strong>{marketDecisionView.title}</strong>
<p>{marketDecisionView.reason}</p>
</div>
<div className="scan-ai-market-decision-stats">
<small>
{isEn ? "Bucket" : "温度桶"} <b>{marketDecisionView.bucketLabel}</b>
</small>
<small>
{isEn ? "YES buy" : "YES 买价"} <b>{marketDecisionView.priceText}</b>
</small>
<small>
{isEn ? "Model-market" : "模型-市场差"} <b>{marketDecisionView.edgeText}</b>
</small>
</div>
{marketDecisionView.marketUrl ? (
<a
className="scan-ai-market-link"
href={marketDecisionView.marketUrl}
target="_blank"
rel="noreferrer"
>
{isEn ? "Open market" : "打开市场"}
</a>
) : null}
</div>
</>
);
}