feat: implement scan terminal mobile dashboard components and data management logic
This commit is contained in:
@@ -98,7 +98,12 @@ function ScanTerminalScreen() {
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !window.matchMedia) return;
|
||||
const media = window.matchMedia("(max-width: 768px)");
|
||||
const syncMobileViewport = () => setIsMobileViewport(media.matches);
|
||||
const syncMobileViewport = () => {
|
||||
setIsMobileViewport(media.matches);
|
||||
if (media.matches) {
|
||||
setActiveView((current) => (current === "map" ? "city-list" : current));
|
||||
}
|
||||
};
|
||||
syncMobileViewport();
|
||||
media.addEventListener("change", syncMobileViewport);
|
||||
return () => media.removeEventListener("change", syncMobileViewport);
|
||||
@@ -336,6 +341,76 @@ function ScanTerminalScreen() {
|
||||
}, []);
|
||||
|
||||
const renderMainView = () => {
|
||||
if (resolvedView === "city-list") {
|
||||
return (
|
||||
<div className="scan-mobile-city-list-view">
|
||||
<div className="scan-mobile-city-list-head">
|
||||
<div>
|
||||
<span>{isEn ? "Lightweight mobile entry" : "移动端轻量入口"}</span>
|
||||
<strong>{isEn ? "Pick one city to inspect" : "选择一个城市进入决策卡"}</strong>
|
||||
</div>
|
||||
<p>
|
||||
{isEn
|
||||
? "Cached scan rows are used first. The map and heavier evidence load only when opened."
|
||||
: "优先使用缓存扫描结果;地图和重证据只在打开后加载。"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="scan-mobile-city-list">
|
||||
{timeSortedRows.slice(0, 24).map((row, index) => {
|
||||
const cityName = row.city || row.city_display_name || row.display_name || "";
|
||||
const display = row.city_display_name || row.display_name || cityName;
|
||||
const currentTemp = row.current_temp ?? row.current_max_so_far ?? null;
|
||||
const deb = row.deb_prediction ?? null;
|
||||
return (
|
||||
<button
|
||||
key={row.id || `${cityName}-${index}`}
|
||||
type="button"
|
||||
className="scan-mobile-city-row"
|
||||
onClick={() => handleOpenDecisionRow(row)}
|
||||
>
|
||||
<span className="scan-mobile-city-rank">{String(index + 1).padStart(2, "0")}</span>
|
||||
<span className="scan-mobile-city-main">
|
||||
<b>{display}</b>
|
||||
<small>{row.market_question || row.target_label || row.local_time || "--"}</small>
|
||||
</span>
|
||||
<span className="scan-mobile-city-metrics">
|
||||
<b>
|
||||
{currentTemp != null && Number.isFinite(Number(currentTemp))
|
||||
? `${Number(currentTemp).toFixed(1)}${row.temp_symbol || "°C"}`
|
||||
: "--"}
|
||||
</b>
|
||||
<small>
|
||||
DEB{" "}
|
||||
{deb != null && Number.isFinite(Number(deb))
|
||||
? `${Number(deb).toFixed(1)}${row.temp_symbol || "°C"}`
|
||||
: "--"}
|
||||
</small>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{!timeSortedRows.length ? (
|
||||
<div className="scan-empty-state">
|
||||
<div className="scan-empty-title">
|
||||
{scanLoading
|
||||
? isEn
|
||||
? "Loading cached scan"
|
||||
: "正在读取缓存扫描"
|
||||
: isEn
|
||||
? "No city rows yet"
|
||||
: "暂无城市列表"}
|
||||
</div>
|
||||
<div className="scan-empty-copy">
|
||||
{isEn
|
||||
? "Use refresh only when you need a new server-side scan."
|
||||
: "只有需要新的服务端扫描时再手动刷新。"}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (resolvedView === "map") {
|
||||
return (
|
||||
<div className="scan-map-view">
|
||||
@@ -396,6 +471,7 @@ function ScanTerminalScreen() {
|
||||
<div
|
||||
className={clsx(
|
||||
"scan-terminal",
|
||||
resolvedView === "city-list" && "city-list-view-active",
|
||||
resolvedView === "map" && "map-view-active",
|
||||
resolvedView !== "map" && "focus-view-active",
|
||||
resolvedView === "analysis" && "analysis-view-active",
|
||||
@@ -432,6 +508,17 @@ function ScanTerminalScreen() {
|
||||
<section className="scan-list-section">
|
||||
<div className="scan-list-header">
|
||||
<div className="scan-list-tabs" role="tablist" aria-label={isEn ? "Content view" : "内容视图"}>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={resolvedView === "city-list"}
|
||||
className={resolvedView === "city-list" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setActiveView("city-list");
|
||||
}}
|
||||
>
|
||||
{isEn ? "City List" : "城市列表"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
|
||||
Reference in New Issue
Block a user