feat(版本详情): 优化任务统计与测试轮次
This commit is contained in:
@@ -12,10 +12,12 @@ import { DevTaskRow } from './DevTaskRow';
|
||||
import { DevTaskCreateModal } from './DevTaskCreateModal';
|
||||
import { DevTaskDetailDrawer } from './DevTaskDetailDrawer';
|
||||
import { calcGroupProgress, DEV_TASK_STATUS_LABEL, aggregateDevTaskHours, devTaskIntervals } from '@/lib/dev-task';
|
||||
import { formatWorkHours, calcTwoMetrics } from '@/lib/work-hours';
|
||||
import { calcTwoMetrics } from '@/lib/work-hours';
|
||||
import { Pagination, usePagination } from '@/components/Pagination';
|
||||
import { SearchInput, matchTitleOrNo } from '@/components/SearchInput';
|
||||
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
|
||||
import { orderByRequirementForGrouping } from '@/lib/requirement-grouping';
|
||||
import { buildEffortSummaryMetrics, sumPositiveHours } from '@/lib/effort-summary';
|
||||
import type { DevTask, DevTaskStatus } from '@/lib/dev-task';
|
||||
|
||||
interface Props {
|
||||
@@ -60,14 +62,25 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
|
||||
return result;
|
||||
}, [versionTasks, filterAssignee, filterStatus, filterBlocked, filterCategory, debouncedKeyword]);
|
||||
|
||||
const { paged, page, setPage, total, pageSize, setPageSize } = usePagination(filteredTasks, 20);
|
||||
const orderedTasks = useMemo(
|
||||
() => orderByRequirementForGrouping(filteredTasks, requirementIds, (task) => task.requirementId),
|
||||
[filteredTasks, requirementIds],
|
||||
);
|
||||
|
||||
const { paged, page, setPage, total, pageSize, setPageSize } = usePagination(orderedTasks, 20);
|
||||
|
||||
const progress = calcGroupProgress(versionTasks);
|
||||
const { estimate: totalEstimate } = aggregateDevTaskHours(versionTasks);
|
||||
const { calendarHours, manhours } = useMemo(() => calcTwoMetrics(devTaskIntervals(versionTasks)), [versionTasks]);
|
||||
const overrun = manhours > totalEstimate && totalEstimate > 0;
|
||||
const underrun = manhours > 0 && manhours < totalEstimate;
|
||||
const hoursTone = overrun ? 'text-red-600' : underrun ? 'text-emerald-600' : 'text-[var(--ink-muted)]';
|
||||
const { actual: actualHours } = aggregateDevTaskHours(versionTasks);
|
||||
const { manhours } = useMemo(() => calcTwoMetrics(devTaskIntervals(versionTasks)), [versionTasks]);
|
||||
const effortMetrics = useMemo(
|
||||
() => buildEffortSummaryMetrics({
|
||||
aiEstimateHours: sumPositiveHours(versionTasks, (task) => task.aiEstimateHours),
|
||||
estimateHours: sumPositiveHours(versionTasks, (task) => task.estimateHours),
|
||||
actualHours,
|
||||
manhours,
|
||||
}),
|
||||
[versionTasks, actualHours, manhours],
|
||||
);
|
||||
const blockedCount = versionTasks.filter((t) => t.isBlocked).length;
|
||||
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
@@ -112,7 +125,12 @@ export function DevTaskTab({ versionId, requirementIds, versionDeadline }: Props
|
||||
<div className="h-1.5 w-20 rounded-full bg-[var(--bg)] overflow-hidden shrink-0">
|
||||
<div className="h-full rounded-full bg-[var(--accent)]" style={{ width: `${progress}%` }} />
|
||||
</div>
|
||||
<span className="text-[11px] text-[var(--ink-muted)] shrink-0">{versionTasks.length}任务 · 预 {formatWorkHours(totalEstimate)}{manhours > 0 ? <> · 日历 {formatWorkHours(calendarHours)} / 人力 <span className={`tabular-nums ${hoursTone}`}>{formatWorkHours(manhours)}</span></> : null}</span>
|
||||
<span className="text-[11px] text-[var(--ink-muted)] shrink-0">{versionTasks.length}任务</span>
|
||||
{effortMetrics.map((metric) => (
|
||||
<span key={metric.label} className="text-[11px] text-[var(--ink-muted)] shrink-0">
|
||||
{metric.label} <span className="tabular-nums text-[var(--ink)]">{metric.value}</span>
|
||||
</span>
|
||||
))}
|
||||
{blockedCount > 0 && <span className="text-[10px] text-red-500 bg-red-50 px-1.5 py-0.5 rounded shrink-0">{blockedCount}阻塞</span>}
|
||||
|
||||
<span className="w-px h-4 bg-[var(--line)] mx-1 shrink-0" />
|
||||
|
||||
Reference in New Issue
Block a user