From 477d18d798c43d16fb223d9cca89b4de899bdf05 Mon Sep 17 00:00:00 2001 From: 777genius Date: Sun, 31 May 2026 06:25:28 +0300 Subject: [PATCH] perf(renderer): memoize active task summary data --- .../team/activity/ActiveTasksBlock.tsx | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/renderer/components/team/activity/ActiveTasksBlock.tsx b/src/renderer/components/team/activity/ActiveTasksBlock.tsx index 0d724de9..a52a37fc 100644 --- a/src/renderer/components/team/activity/ActiveTasksBlock.tsx +++ b/src/renderer/components/team/activity/ActiveTasksBlock.tsx @@ -1,4 +1,4 @@ -import { memo, type ReactNode, useState } from 'react'; +import { memo, type ReactNode, useMemo, useState } from 'react'; import { useAppTranslation } from '@features/localization/renderer'; import { CARD_BG, CARD_BORDER_STYLE, CARD_ICON_MUTED } from '@renderer/constants/cssVariables'; @@ -46,33 +46,45 @@ export const ActiveTasksBlock = memo(function ActiveTasksBlock({ const { t } = useAppTranslation('team'); const { isLight } = useTheme(); const [collapsed, setCollapsed] = useState(defaultCollapsed); - const colorMap = buildMemberColorMap(members); - const avatarMap = buildMemberAvatarMap(members); - const taskMap = new Map(tasks.map((t) => [t.id, t])); - const entries: ActivityEntry[] = []; - - // Members working on tasks - const workingMemberNames = new Set(); - for (const m of members) { - if (!m.currentTaskId) continue; - const task = taskMap.get(m.currentTaskId); - // Defense-in-depth: hide stale currentTaskId until backend refresh clears it. - if (!isDisplayableCurrentTask(task)) continue; - workingMemberNames.add(m.name); - entries.push({ member: m, task, taskId: m.currentTaskId, kind: 'working' }); - } - - // Members reviewing tasks (only if not already shown as working) - for (const m of members) { - if (workingMemberNames.has(m.name)) continue; - const reviewTask = tasks.find( - (t) => t.reviewer === m.name && getTeamTaskWorkflowColumn(t) === 'review' - ); - if (reviewTask) { - entries.push({ member: m, task: reviewTask, taskId: reviewTask.id, kind: 'reviewing' }); + const colorMap = useMemo(() => buildMemberColorMap(members), [members]); + const avatarMap = useMemo(() => buildMemberAvatarMap(members), [members]); + const entries = useMemo(() => { + const taskMap = new Map(tasks.map((task) => [task.id, task])); + const reviewTaskByReviewer = new Map(); + for (const task of tasks) { + if (!task.reviewer || getTeamTaskWorkflowColumn(task) !== 'review') continue; + if (!reviewTaskByReviewer.has(task.reviewer)) { + reviewTaskByReviewer.set(task.reviewer, task); + } } - } + + const nextEntries: ActivityEntry[] = []; + const workingMemberNames = new Set(); + for (const member of members) { + if (!member.currentTaskId) continue; + const task = taskMap.get(member.currentTaskId); + // Defense-in-depth: hide stale currentTaskId until backend refresh clears it. + if (!isDisplayableCurrentTask(task)) continue; + workingMemberNames.add(member.name); + nextEntries.push({ member, task, taskId: member.currentTaskId, kind: 'working' }); + } + + for (const member of members) { + if (workingMemberNames.has(member.name)) continue; + const reviewTask = reviewTaskByReviewer.get(member.name); + if (reviewTask) { + nextEntries.push({ + member, + task: reviewTask, + taskId: reviewTask.id, + kind: 'reviewing', + }); + } + } + + return nextEntries; + }, [members, tasks]); if (entries.length === 0) return null;