perf(team): isolate task duration ticking
This commit is contained in:
parent
5e3e77bf65
commit
8edff2f0c3
1 changed files with 71 additions and 42 deletions
|
|
@ -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<typeof calculateTaskImplementationDuration>;
|
||||
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 (
|
||||
<span
|
||||
className="inline-flex items-center gap-1 rounded-md bg-[var(--color-bg-secondary)] px-1.5 py-0.5 text-[10px] font-normal leading-none text-[var(--color-text-muted)]"
|
||||
title={t('taskDetail.workflow.implementationTimeTitle')}
|
||||
>
|
||||
<Clock size={10} />
|
||||
<span>
|
||||
{t('taskDetail.workflow.inProgressTime', {
|
||||
duration: formatTaskImplementationDuration(duration.elapsedMs),
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
const WorkflowTimelineWithDuration = memo(function WorkflowTimelineWithDuration({
|
||||
task,
|
||||
events,
|
||||
memberColorMap,
|
||||
}: {
|
||||
task: TeamTaskWithKanban;
|
||||
events: NonNullable<TeamTaskWithKanban['historyEvents']>;
|
||||
memberColorMap: Map<string, string>;
|
||||
}): React.JSX.Element {
|
||||
const { nowMs } = useTaskImplementationDurationClock(task);
|
||||
return (
|
||||
<WorkflowTimeline
|
||||
events={events}
|
||||
memberColorMap={memberColorMap}
|
||||
implementationDurationTask={task}
|
||||
nowMs={nowMs}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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 (
|
||||
<Dialog open={open} onOpenChange={(v) => !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 ? (
|
||||
<span
|
||||
className="inline-flex items-center gap-1 rounded-md bg-[var(--color-bg-secondary)] px-1.5 py-0.5 text-[10px] font-normal leading-none text-[var(--color-text-muted)]"
|
||||
title={t('taskDetail.workflow.implementationTimeTitle')}
|
||||
>
|
||||
<Clock size={10} />
|
||||
<span>
|
||||
{t('taskDetail.workflow.inProgressTime', {
|
||||
duration: taskImplementationDurationLabel,
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
headerExtra={<TaskImplementationDurationBadge task={currentTask} />}
|
||||
defaultOpen={false}
|
||||
>
|
||||
<WorkflowTimeline
|
||||
<WorkflowTimelineWithDuration
|
||||
task={currentTask}
|
||||
events={currentTask.historyEvents}
|
||||
memberColorMap={colorMap}
|
||||
implementationDurationTask={currentTask}
|
||||
nowMs={taskDurationNowMs}
|
||||
/>
|
||||
</CollapsibleTeamSection>
|
||||
) : null}
|
||||
|
|
|
|||
Loading…
Reference in a new issue