Keep dense dashboard lists stable during selection changes

Opportunity and calendar cards now isolate item rendering behind memoized components. This preserves the current action grouping and copy while preventing selection or parent dashboard updates from re-rendering every dense card body.

Constraint: Do not change ranking, grouping, or product wording in this performance pass.

Rejected: Introduce virtual list dependency | the current list size can benefit from memo boundaries first without new dependencies.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Tested: TypeScript diagnostics for CalendarView and OpportunityOverview

Tested: npm run build

Not-tested: Browser profiler capture with a large production city set.
This commit is contained in:
2569718930@qq.com
2026-04-28 11:25:14 +08:00
parent 2e5bdfd1b6
commit 8d05812a7e
2 changed files with 83 additions and 57 deletions
@@ -1,4 +1,4 @@
import { useMemo } from "react";
import { memo, useMemo } from "react";
import clsx from "clsx";
import type { ScanOpportunityRow, ScanTerminalResponse } from "@/lib/dashboard-types";
import { formatTemperatureValue } from "@/lib/dashboard-utils";
@@ -13,7 +13,7 @@ import {
pickOpportunitySections,
} from "@/components/dashboard/scan-terminal/decision-utils";
function OpportunityDecisionCard({
const OpportunityDecisionCard = memo(function OpportunityDecisionCard({
row,
locale,
selected,
@@ -91,7 +91,7 @@ function OpportunityDecisionCard({
</button>
</article>
);
}
});
export function OpportunityOverview({
rows,
@@ -116,7 +116,10 @@ export function OpportunityOverview({
}) {
const isEn = locale === "en-US";
const sections = useMemo(() => pickOpportunitySections(rows, locale), [locale, rows]);
const visibleSections = sections.filter((section) => section.rows.length > 0);
const visibleSections = useMemo(
() => sections.filter((section) => section.rows.length > 0),
[sections],
);
const summary = terminalData?.summary;
if (loading) {