diff --git a/src/renderer/components/team/dialogs/TaskDetailDialog.tsx b/src/renderer/components/team/dialogs/TaskDetailDialog.tsx index f5d69c8b..7ecb6f2e 100644 --- a/src/renderer/components/team/dialogs/TaskDetailDialog.tsx +++ b/src/renderer/components/team/dialogs/TaskDetailDialog.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useAppTranslation } from '@features/localization/renderer'; import { api } from '@renderer/api'; @@ -138,6 +138,73 @@ interface TaskDetailDialogProps { headerExtra?: React.ReactNode; } +function useTaskImplementationDurationClock(task: TeamTaskWithKanban): { + duration: ReturnType; + nowMs: number; +} { + const [nowMs, setNowMs] = useState(() => Date.now()); + const duration = useMemo(() => calculateTaskImplementationDuration(task, nowMs), [task, nowMs]); + + useEffect(() => { + if (!duration.hasRunningInterval) return; + + setNowMs(Date.now()); + const intervalId = window.setInterval(() => { + setNowMs(Date.now()); + }, 1000); + + return () => window.clearInterval(intervalId); + }, [duration.hasRunningInterval, task.id]); + + return { duration, nowMs }; +} + +const TaskImplementationDurationBadge = memo(function TaskImplementationDurationBadge({ + task, +}: { + task: TeamTaskWithKanban; +}): React.JSX.Element | null { + const { t } = useAppTranslation('team'); + const { duration } = useTaskImplementationDurationClock(task); + if (!shouldShowTaskImplementationDuration(duration)) { + return null; + } + + return ( + + + + {t('taskDetail.workflow.inProgressTime', { + duration: formatTaskImplementationDuration(duration.elapsedMs), + })} + + + ); +}); + +const WorkflowTimelineWithDuration = memo(function WorkflowTimelineWithDuration({ + task, + events, + memberColorMap, +}: { + task: TeamTaskWithKanban; + events: NonNullable; + memberColorMap: Map; +}): React.JSX.Element { + const { nowMs } = useTaskImplementationDurationClock(task); + return ( + + ); +}); + export const TaskDetailDialog = ({ open, loading = false, @@ -633,29 +700,6 @@ export const TaskDetailDialog = ({ : undefined : undefined; - const [taskDurationNowMs, setTaskDurationNowMs] = useState(() => Date.now()); - const taskImplementationDuration = useMemo( - () => calculateTaskImplementationDuration(currentTask, taskDurationNowMs), - [currentTask, taskDurationNowMs] - ); - const showTaskImplementationDuration = shouldShowTaskImplementationDuration( - taskImplementationDuration - ); - const taskImplementationDurationLabel = formatTaskImplementationDuration( - taskImplementationDuration.elapsedMs - ); - - useEffect(() => { - if (!open || !taskImplementationDuration.hasRunningInterval) return; - - setTaskDurationNowMs(Date.now()); - const intervalId = window.setInterval(() => { - setTaskDurationNowMs(Date.now()); - }, 1000); - - return () => window.clearInterval(intervalId); - }, [open, taskImplementationDuration.hasRunningInterval, currentTask?.id]); - if (loading) { return ( !v && onClose()}> @@ -1493,28 +1537,13 @@ export const TaskDetailDialog = ({ contentClassName="pl-2.5" headerClassName="-mx-6 w-[calc(100%+3rem)]" headerContentClassName="pl-6" - headerExtra={ - showTaskImplementationDuration ? ( - - - - {t('taskDetail.workflow.inProgressTime', { - duration: taskImplementationDurationLabel, - })} - - - ) : undefined - } + headerExtra={} defaultOpen={false} > - ) : null}