diff --git a/frontend/components/dashboard/scan-terminal/CalendarView.tsx b/frontend/components/dashboard/scan-terminal/CalendarView.tsx index d76fee56..af76e189 100644 --- a/frontend/components/dashboard/scan-terminal/CalendarView.tsx +++ b/frontend/components/dashboard/scan-terminal/CalendarView.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { memo, useEffect, useMemo, useState } from "react"; import { getWindowPhaseMeta } from "@/components/dashboard/OpportunityTable"; import type { ScanOpportunityRow } from "@/lib/dashboard-types"; import { getLocalizedCityName } from "@/lib/dashboard-home-copy"; @@ -26,6 +26,12 @@ type CalendarActionGroup = { sort: number; }; +type CalendarActionItem = { + row: ScanOpportunityRow; + meta: CalendarMeta; + reason: string; +}; + function normalizeCalendarCityKey(value?: string | null) { return String(value || "") .trim() @@ -291,6 +297,66 @@ function buildCalendarCoreReason( : "先放入行动面板,等待下一轮观测"; } +const CalendarActionCard = memo(function CalendarActionCard({ + item, + locale, + selected, + onSelectRow, +}: { + item: CalendarActionItem; + locale: string; + selected: boolean; + onSelectRow: (row: ScanOpportunityRow) => void; +}) { + const { row, meta, reason } = item; + const tempSymbol = row.temp_symbol || "°C"; + const phaseMeta = getWindowPhaseMeta(row, locale); + + return ( + + ); +}); + export function CalendarView({ rows, locale, @@ -324,7 +390,7 @@ export function CalendarView({ label: string; subtitle: string; sort: number; - items: Array<{ row: ScanOpportunityRow; meta: CalendarMeta; reason: string }>; + items: CalendarActionItem[]; } >(); dedupeCalendarRows(rows).forEach((row) => { @@ -385,57 +451,14 @@ export function CalendarView({
- {group.items.map(({ row, meta, reason }) => ( - + {group.items.map((item) => ( + ))}
diff --git a/frontend/components/dashboard/scan-terminal/OpportunityOverview.tsx b/frontend/components/dashboard/scan-terminal/OpportunityOverview.tsx index 638af1e8..3e5b567f 100644 --- a/frontend/components/dashboard/scan-terminal/OpportunityOverview.tsx +++ b/frontend/components/dashboard/scan-terminal/OpportunityOverview.tsx @@ -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({ ); -} +}); 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) {