Files
PolyWeather/frontend/components/dashboard/opportunity-target.ts
T
2569718930@qq.com 2b1d7c0b65 Improve dashboard maintainability before the next release
The dashboard had several oversized orchestration, component, and CSS files that made product-copy changes and mobile/performance work risky. This refactor preserves behavior while splitting scan terminal CSS, opportunity helpers, future forecast panels, history/detail charts, and probability/model sections into smaller ownership boundaries.

Constraint: No user-visible version bump because this batch is architecture and performance cleanup, not a release announcement.

Rejected: Rewrite dashboard state management in the same batch | too broad for a safe upload after CSS and component splitting.

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Keep new component/CSS boundaries instead of moving product copy back into the large dashboard files.

Tested: npm run build; npm run test:business; git diff --check

Not-tested: Browser visual smoke test after push
2026-04-28 20:19:17 +08:00

49 lines
1.6 KiB
TypeScript

import type { ScanOpportunityRow } from "@/lib/dashboard-types";
import { normalizeTemperatureLabel } from "@/lib/temperature-utils";
export function normalizeBucketLabel(value?: string | null, tempSymbol?: string | null) {
return normalizeTemperatureLabel(value, tempSymbol)
.toLowerCase()
.replace(/\s+/g, "")
.replace(/℃/g, "°c");
}
export function extractNumbers(value?: string | null) {
return Array.from(String(value || "").matchAll(/-?\d+(?:\.\d+)?/g)).map((match) =>
Number(match[0]),
);
}
export function getTargetRange(row: ScanOpportunityRow) {
const lower =
row.target_lower != null && Number.isFinite(Number(row.target_lower))
? Number(row.target_lower)
: null;
const upper =
row.target_upper != null && Number.isFinite(Number(row.target_upper))
? Number(row.target_upper)
: null;
if (lower != null || upper != null) return { lower, upper };
const rawLabel = String(row.target_label || row.action || "");
const numbers = extractNumbers(rawLabel);
if (numbers.length >= 2) {
return { lower: Math.min(numbers[0], numbers[1]), upper: Math.max(numbers[0], numbers[1]) };
}
const value =
row.target_threshold ??
row.target_value ??
(numbers.length ? numbers[0] : null);
if (value == null || !Number.isFinite(Number(value))) {
return { lower: null, upper: null };
}
const numeric = Number(value);
if (/(\+|above|higher|or\s+higher|>=|≥|以上)/i.test(rawLabel)) {
return { lower: numeric, upper: null };
}
if (/(below|or\s+below|<=|≤|以下)/i.test(rawLabel)) {
return { lower: null, upper: numeric };
}
return { lower: numeric, upper: numeric };
}