diff --git a/src/renderer/components/sidebar/GlobalTaskList.tsx b/src/renderer/components/sidebar/GlobalTaskList.tsx index b3cec963..9427817e 100644 --- a/src/renderer/components/sidebar/GlobalTaskList.tsx +++ b/src/renderer/components/sidebar/GlobalTaskList.tsx @@ -194,6 +194,10 @@ function buildTaskTeamSummary(task: GlobalTask): TeamSummary { type TaskRowAction = (teamName: string, taskId: string) => void; type TaskRowDeleteAction = (teamName: string, taskId: string) => void | Promise; type TaskDisplaySubjectResolver = (task: GlobalTask) => string | undefined; +type TaskBooleanResolver = (teamName: string, taskId: string) => boolean; +type TeamBooleanResolver = (teamName: string) => boolean; +type TaskOwnerColorResolver = (task: GlobalTask) => string | null | undefined; +type TeamHeaderFormatter = (teamDisplayName: string) => string; interface GlobalTaskRowProps { task: GlobalTask; @@ -288,6 +292,112 @@ const GlobalTaskRow = memo(function GlobalTaskRow({ ); }); +interface TaskRowsProps { + tasks: GlobalTask[]; + visibleCount?: number; + keyPrefix?: string; + isPinned: TaskBooleanResolver; + isArchived: TaskBooleanResolver; + isNewTask: (task: GlobalTask) => boolean; + isTeamOffline: TeamBooleanResolver; + renamingKey: string | null; + hideTeamName?: boolean; + hideProjectName?: boolean; + showTeamName?: boolean; + showTeamHeader?: boolean; + pinnedOverride?: boolean; + archivedOverride?: boolean; + formatTeamHeader?: TeamHeaderFormatter; + onTogglePin: TaskRowAction; + onToggleArchive: TaskRowAction; + onMarkUnread: TaskRowAction; + onRename: TaskRowAction; + onDelete: TaskRowDeleteAction; + onRenameComplete: (teamName: string, taskId: string, newSubject: string) => void; + onRenameCancel: () => void; + getDisplaySubject: TaskDisplaySubjectResolver; + getOwnerColorName: TaskOwnerColorResolver; +} + +const TaskRows = memo(function TaskRows({ + tasks, + visibleCount, + keyPrefix = '', + isPinned, + isArchived, + isNewTask, + isTeamOffline, + renamingKey, + hideTeamName, + hideProjectName, + showTeamName, + showTeamHeader, + pinnedOverride, + archivedOverride, + formatTeamHeader, + onTogglePin, + onToggleArchive, + onMarkUnread, + onRename, + onDelete, + onRenameComplete, + onRenameCancel, + getDisplaySubject, + getOwnerColorName, +}: TaskRowsProps): React.JSX.Element { + let lastTeam: string | null = null; + const visibleTasks = typeof visibleCount === 'number' ? tasks.slice(0, visibleCount) : tasks; + + return ( + <> + {visibleTasks.map((task) => { + const taskKey = `${keyPrefix}${task.teamName}-${task.id}`; + const row = ( + + ); + + if (!showTeamHeader || !formatTeamHeader) { + return row; + } + + const shouldShowTeamHeader = task.teamName !== lastTeam; + lastTeam = task.teamName; + + return ( +
+ {shouldShowTeamHeader && ( +
+ {formatTeamHeader(task.teamDisplayName)} +
+ )} + {row} +
+ ); + })} + + ); +}); + export const GlobalTaskList = memo(function GlobalTaskList({ hideHeader = false, filters: externalFilters, @@ -515,6 +625,14 @@ export const GlobalTaskList = memo(function GlobalTaskList({ }, [memberColorByTeam] ); + const isTeamOffline = useCallback( + (teamName: string): boolean => offlineTeamNames.has(teamName), + [offlineTeamNames] + ); + const formatTeamHeader = useCallback( + (teamDisplayName: string): string => t('tasksPanel.teamLabel', { team: teamDisplayName }), + [t] + ); const setGroupingMode = (mode: TaskGroupingMode): void => { setGroupingModeState(mode); @@ -708,6 +826,7 @@ export const GlobalTaskList = memo(function GlobalTaskList({ () => filtered.filter((t) => !taskLocalState.isPinned(t.teamName, t.id)), [filtered, taskLocalState] ); + const sortedPinnedTasks = useMemo(() => sortTasksByFreshness(pinnedTasks), [pinnedTasks]); const sortedFlat = useMemo( () => applySortMode(normalTasks, sortMode, readState), @@ -736,6 +855,10 @@ export const GlobalTaskList = memo(function GlobalTaskList({ syncProjectGroupVisibleCountByKey(projectRequestedVisibleCountByKey, projectGroupVisibility), [projectRequestedVisibleCountByKey, projectGroupVisibility] ); + const taskFilterTeams = useMemo( + () => teams.map((team) => ({ teamName: team.teamName, displayName: team.displayName })), + [teams] + ); const projectCollapsed = useCollapsedGroups('project', projectGroupKeys); const timeCollapsed = useCollapsedGroups('time', timeGroupKeys); @@ -838,7 +961,7 @@ export const GlobalTaskList = memo(function GlobalTaskList({ ({ teamName: t.teamName, displayName: t.displayName }))} + teams={taskFilterTeams} projectOptions={projectFilterOptions} filters={filters} onFiltersChange={setFilters} @@ -853,27 +976,27 @@ export const GlobalTaskList = memo(function GlobalTaskList({ {t('tasksPanel.pinned')} - {sortTasksByFreshness(pinnedTasks).map((task) => ( - - ))} + )} @@ -956,28 +1079,26 @@ export const GlobalTaskList = memo(function GlobalTaskList({ )} - {groupingMode === 'none' && - sortedFlat.map((task) => ( - - ))} + {groupingMode === 'none' && ( + + )} {groupingMode === 'project' && projectGroups.map((group) => { @@ -991,10 +1112,8 @@ export const GlobalTaskList = memo(function GlobalTaskList({ projectVisibleCountByKey[group.projectKey], group.tasks.length ); - const visibleTasks = group.tasks.slice(0, visibleCount); const showMoreVisible = canProjectGroupShowMore(visibleCount, group.tasks.length); const showLessVisible = canProjectGroupShowLess(visibleCount, group.tasks.length); - let lastTeam: string | null = null; return (
- {!isGroupCollapsed && - visibleTasks.map((task) => { - const showTeamHeader = task.teamName !== lastTeam; - lastTeam = task.teamName; - return ( -
- {showTeamHeader && ( -
- {t('tasksPanel.teamLabel', { team: task.teamDisplayName })} -
- )} - -
- ); - })} + {!isGroupCollapsed && ( + + )} {!isGroupCollapsed && (showMoreVisible || showLessVisible) && (
{showMoreVisible && ( @@ -1108,7 +1218,6 @@ export const GlobalTaskList = memo(function GlobalTaskList({ categories.map((category) => { const tasks = grouped[category]; const isGroupCollapsed = timeCollapsed.isCollapsed(category); - let lastTeam: string | null = null; return (
@@ -1129,38 +1238,27 @@ export const GlobalTaskList = memo(function GlobalTaskList({ - {!isGroupCollapsed && - tasks.map((task) => { - const showTeamHeader = task.teamName !== lastTeam; - lastTeam = task.teamName; - - return ( -
- {showTeamHeader && ( -
- {t('tasksPanel.teamLabel', { team: task.teamDisplayName })} -
- )} - -
- ); - })} + {!isGroupCollapsed && ( + + )}
); })}